目录
说在前面
请教Hpdoger师傅Node.js的问题时,他给了我一道HackTM CTF 2020的Node.js题。花了几个小时看也没有很好的解决。最后还是他给了思路才把想整个过程理清楚。由于才刚刚学习node.js,文章中如果出现问题还希望师傅们指出来,十分感谢。
解题思路
题目界面:
题目部分源码:
1 |
const express = require("express"); |
整个题目就是一个在线画图的程序,当输入用户名登录之后就可以对网页上的颜色格子进行操作。(这不是重点)服务端使用了express
框架,并使用express-jwt
来进行用户验证。
比较重要的页面分别是:
/init
获取POST数据中的p和q参数,最终生成一个adminId,返回一个id=adminId的用户token/serverInfo
根据用户的权限返回config内的信息/updateUser
更新用户信息,设置用户权限/login
登录账户/flag
获取Flag
逆推整个过程的话,大概是这样的思路:
怎么获取Flag?
访问Flag页面需要对adminId进行判断,adminId需要为0才能获取得到Flag。而adminId是可以通过/init传递p和q参数进行设置的。怎么将adminId设置为0呢?
怎么将adminId设置为0?
来具体看一下/init
页面,它会获取POST数据中的p和q参数,并最终生成一个adminId:
1 |
app.post("/init", (req, res) => { |
- node.js中map可以参考:Array.prototype.map()
- reduce可以参考:Array.prototype.reduce()
一些语句已经作了注释,最重要的在后面的map((c, i) => c.charCodeAt(0) ^ target.charCodeAt(i))
,这个语句的作用就是将pwHash中的每一位与target中的相同位置的字符进行异或。最后reduce将异或后的值进行取和。
pwnHash的来源是md5(p*q)
,target的来源是md5(n)
,如果想要它们异或后再取和的值为0的话,我们该怎么做呢?
我们首先得知道n的值是多少。在知道n的情况下,将p设置为n的值,q设置为1。(qp互换也可以)这样pwnHash和target的值就会相同。相同的值进行异或就会为0,取和之后也为0。这样就可以使adminId为0了。页面最终还会返回id为0的token,利用token就可以获取flag了。
那现在的问题就是怎么得到n的值。
怎么获取n的值?
在源码中可以知道,/serverInfo
会根据用户的权限(right)返回config内的信息。默认获取得到的值中是没有n的,所以我们需要通过/updateUser
页面来设置当前用户查看config信息的权限。
先来看下/updateUser
页面的源码:
1 |
app.post("/updateUser", (req, res) => { |
数据包格式为:
1 |
{ |
rights部分就是要添加查看的权限。
先不看前面是否为管理员的判断,直接看后面添加权限时的判断。这里调用了checkRights()
来进行权限检查。
1 |
function checkRights(arr) { |
函数中设置了一个黑名单,不允许p、n、port
被添加进用户的查看权限中。验证方式是用for循环获取每一个权限,查看其是否在blacklist中。如果存在就返回false,不存在就返回true。所以传递rights的不能直接传递”n”。
先来看/serverInfo
页面是怎么获取config的值的,
1 |
app.get("/serverInfo", (req, res) => { |
这里获取config的值就是通过config[i]获取的(i是键名)。那如何不直接传递”n”而得到n的值呢?
这里要了解javascript中数组取值的方式。定义一个array1数组如图,注意赋值时的参数值:
可以看到,我这里传递给array1的键值是一个多维数组,但是同样可以获取得到数组中键名为”port”的值。(这种取值的方式在python、php中不行)
由于这样的取值方式是可行的,所以我们只需要给right赋值一个["n"]
就可以绕过前面的黑名单了。
好,现在可以取到n了,再来看看怎么登陆管理员用户名。
怎么使用管理员用户名登陆?
登陆页面/login
中有一个函数用于判断用户名是否为合理的用户名:
1 |
function isValidUser(u) { |
在/updateUser
页面中有一个函数用于判断用户是否为管理员:
1 |
function isAdmin(u) { |
在登录时,isValidUser
函数会对用户输入的用户名进行toUpperCase
处理,再与管理员用户名进行对比。如果输入的用户名与管理员用户名相同,就不允许登录。
但是我们可以看到,在之后的一个判断用户是否为管理员的函数中,对用户名进行处理的是toLowerCase
。所以这两个差异,就可以使用大小写特性来进行绕过。
大小写差异可以参考p神的这篇文章:Fuzz中的javascript大小写特性
题目中默认的管理员用户名为:hacktm
所以,我们指定登录时的用户名为:hacKtm 即可绕过isValidUser
和isAdmin
的验证。
思路总结
- 利用javascript大小写特性使用管理员的用户名登录
- 给用户添加查看n的权限并查看n的值
- 通过赋值p、q将adminId设置为0
- 获取Flag
过程复现
- 利用javascript大小写特性使用管理员的用户名登录
- 给用户添加查看n的权限并查看n的值
查看n的值:
- 通过赋值p、q将adminId设置为0
- 获取Flag
- By:threezh1.com
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论