关注这个靶场的其它相关笔记:攻防世界(XCTF) —— 靶场笔记合集-CSDN博客
0x01:考点速览
本题考察的是 JS 的代码审计,以下是你需要了解的知识点:
-
String.fromCharCode()
-> 将接收的 Unicode 编码转换成字符或字符串
0x02:Write UP
从题目来看,本题考查的就是一个前端 JS 的代码审计:
进入靶场,直接弹出一个框框,让我们输入 password,这里我们随便输入:
提示我们是假的密码,这里我们忽略,查看目标代码:
好了,接下来就是紧张刺激的代码审计了,下面是 1.0 版本的审计结果:
<script>function dechiffre(pass_enc) {var pass = "70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65"; // 一串神秘的数字var tab = pass_enc.split(','); // 把输入的密文转换成数组var tab2 = pass.split(','); // 把 pass 转换成数组var i, j, k, l = 0,m, n, o, p = "";i = 0;j = tab.length; // j = 输入的密文数组的长度k = j + (l) + (n = 0); // k = j + l + ( n = 0) = j + 0 + 0 = j -> 狗的,k 的大小和也等于输入密文的长度n = tab2.length; // n = pass 数组的长度for (i = (o = 0); i < (k = j = n); i++) { // o 转换为了 number 型,k,j 的长度也重新变为了 pass 数组的长度o = tab[i - l]; // o = tab[i - l] -> l 的值就没变过 -> o = tab[i] -> 依次取出我们输入的密文数组中的元素p += String.fromCharCode((o = tab2[i])); // o = tab2[i] -> 依次取出 pass 数组中的元素,并转换为字符,追加到 p 后if (i == 5) break; // 取 5 个元素后,跳出循环}for (i = (o = 0); i < (k = j = n); i++) { // k,j 的长度依旧为 pass 数组的长度o = tab[i - l]; // 同上个循环一样if (i > 5 && i < k - 1) // k 为 pass 数组的长度, k-1 为 pass 数组最后一个元素的位置 -> < k -1 -> 对方根本没想过要取出最后一个元素p += String.fromCharCode((o = tab2[i])); // 取出 pass 数组中下标从6到倒数第二个元素,并转换为字符,追加到 p 后}p += String.fromCharCode(tab2[17]); // 单独取一个 17 ? -> 纯纯恶心人,你数数 pass 中有 18 组数字,最后一个的下标就是 17pass = p;return pass; // 返回 pass 密文}// String["fromCharCode"]() -> String.fromCharCode() 的另一种写法,该方法接收一个或多个 Unicode 字符编码,返回一个字符串。String["fromCharCode"](dechiffre("\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30"));h = window.prompt('Enter password'); // window.prompt() 获取用户的输入赋值给 halert(dechiffre(h)); // 调用 dechiffre() 函数并传入 h 作为参数 -> 将最终结果弹出</script>
通过 1.0 的代码审计,我们发现了,其最终的输出和我们的输入没有任何关系,另外,它返回的结果也只是把预定的 pass 中的数组转换为字符后的结果,所以上面的代码完全可以简化为下面的样子:
<script>function decode(pass) {var pass_array = pass.split(","); // 将 pass 按 , 号隔开var result = "";for (i = 0; i < pass_array.length; i++) {result += String.fromCharCode(pass_array[i]);}console.log(result);}var pass = "70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65";decode(pass);</script>
其输出的值,和嘲讽我们的值是一样,其实动动脑也能发现,pass 后面的两个数字为 “72,65”,72 可能没法一下子反应,但 65 不就是 A 的 ASCII 码值嘛。
Ok,分析完目标狗的很的加密函数,下面问题就是正确的密钥在哪里?我们可以发现,目标中存在一大串神秘的以 “\x” 开头的字符,并且也同样调用了 dechiffre () 函数,只不过没输出出来。
看到 “\x” 其实很简单就能联想到十六进制字符,那我们就尝试把十六进制字符转成字符串看看是啥(这里我使用的还是开发者工具里的 Console,里面会自动转换):
可以发现,其转换完成后也是一串数字,还是一串用 “,” 号分隔的数字,直接传入我们编写的解密函数中:
其实已经拿到 Flag 了,别忘了题目一开始的提示(Flag 的格式为 Cyberpeace {xxxxxxxxx})。
不知道为啥,写这道题目让我有一个感慨:一个东西,外围防御做的越好,说明他的内部越薄弱。本题用花里胡哨的代码扰乱我们的思路,但其实只做了两件事,把密码转成 ASCII 码值,再把 ASCII 码值转成十六进制,对 Pwn 熟悉的小伙伴估计不用看 JS 代码,凭借对密码格式的熟悉,两部就解出来了(上面写函数的过程完全可以省略的)。
0x03:参考资料
-
JavaScript fromCharCode () 方法