JS-Forward 源码剖析与工作机制解析

admin 2025年6月7日13:31:34评论0 views字数 7272阅读24分14秒阅读模式

JS-Forward 源码剖析与工作机制解析

Js-Forward是一款为了解决在渗透测试过程中所遇到的WEB参数加密而开发出的脚本工具,但是很多师傅对这个工具的实现原理不是很理解,那小天今天就带着大家来学习一下这个工具的源码逻辑和实现原理吧~

1.架构图

作者github的工具地址:https://github.com/G-Security-Team/JS-Forward

以下是作者github的工具架构图

JS-Forward 源码剖析与工作机制解析

直接看这个架构图可能有些师傅还是有些迷茫,没关系,大家往后看。

2.原理解释

浏览器前端JS在对数值进行加密或解密逻辑时,大多无非就是两种逻辑:

  1. 1. 将需要加密的明文参数传入一个加密函数,返回密文。
  2. 2. 将服务器密文传入一个解密函数,返回明文结果

所以无论是加密还是解密,在前端浏览器当中都会有一个明文出现的地方,这时候我们通过JS-Forword这款工具可以将这个时刻的明文值暴露给外界以供修改(如通过burp),然后将修改后的值再传回浏览器使浏览器自身再进行后续的操作。

JS-Forword的原理大致就是如此,那么这款工具是如何实现此过程的呢?来让我们在源码层面进行分析查看。

3.源码分析

首先这款工具的代码还是比较简单的,推荐大家都阅读学习一下,以下是一张逻辑流程简要图。JS-Forward 源码剖析与工作机制解析

3.1导入的模块和全局变量

# 导入线程模块,用于创建和管理线程from threading import Thread# 导入HTTP服务器模块,用于创建HTTP服务器from http.server import HTTPServer, BaseHTTPRequestHandler# 导入requests库,用于发送HTTP请求import requests# 定义转发服务器的端口号 (这里是forword而不是forward不知道是不是作者命名疏忽)FORWORD_PORT = 28080# 定义响应服务器的端口号ECHO_PORT = 38080# 定义Burp代理的端口号BURP_PORT = 8080

代码文件的最顶部当然就算一些模块的导入啦,这里小天提醒大家需要注意的是该脚本的核心功能便是通过HTTPServer 实现了 两个简单服务器类 ,然利用 BaseHTTPRequestHandler 自定义请求和响应的处理逻辑,建议大家有能力可以学一下python的http模块的内容哦~

导入相关模块之后 用全局变量的形式定义了三个端口,通过变量名称和端口号我们可以轻易发现其中一个是burp的默认端口号8080,作者是直接将这三个端口号写死在了程序里面,但是因为这个工具的脚本比较简单,所以也方便直接打开文件后修改,而另外两个端口的作用我也给大家标注在了代码注释中。

不懂没关系,接着往下看,这里就是让大家先有个印象,整个过程会涉及到三个端口的服务操作

3.2脚本程序入口:

if __name__ == '__main__':    banner() #输出JS-Forward的ASCII艺术字体作为logo    flag = True#循环退出标志while flag:        flag = get_payload() #生成在浏览器中发送明文参数到FORWORD端口服务的xhr请求代码# 创建线程对象    t1 = Thread(target=echo_forward_server_thread)    t = Thread(target=echo_server_thread)# 将俩子线程设置为守护线程后启动线程    t.daemon = True    t.start()    t1.daemon = True    t1.start()print(">准备就绪 请启动Burp,端口:8080")# 通过join方法阻塞主线程直到子线程执行完毕for t in [t, t1]:        t.join()

首先输出了一个JS-Forward的ASCII艺术字体作为logo,然后通过flag控制的循环中,利用get_payload()函数用来生成在浏览器中发送明文参数到FORWORD端口服务的xhr请求代码,也就是我们运行脚本后输入参数名称后生成的JS代码payload(如下图)。

JS-Forward 源码剖析与工作机制解析

来让我们看一下get_payload的代码:

defget_payload():# 无限循环,直到用户输入$end来退出while(1):# 打印分隔符,帮助用户区分不同的输入提示print("============================================================================================")  # 提示用户输入要转发到Burp的参数名        param_name = input(">请输入要forward到Burp的参数名(输入$end结束):")    # 如果用户输入$end,结束函数并返回False,退出循环if param_name == "$end":returnFalse# 提示用户输入参数的数据类型(json 或 string)        data_type = input(">请输入" + param_name +  "的数据类型(json/string):"# 提示用户输入请求类型(例如: REQUEST 或 RESPONSE)        request_type = input(">请输入请求标识(例如:REQUEST/RESPONSE):"# 如果数据类型是json,生成相应的payloadif data_type == "json":            base_payload = 'var xhr = new XMLHttpRequest();xhr.open("post", "http://127.0.0.1:' + str(FORWORD_PORT) + '/' + request_type + '", false);xhr.send(JSON.stringify(' + param_name + '));' + param_name + '=JSON.parse(xhr.responseText);'# 如果数据类型是string,生成相应的payloadelif data_type == "string":            base_payload = 'var xhr = new XMLHttpRequest();xhr.open("post", "http://127.0.0.1:' + str(FORWORD_PORT) + '/' + request_type + '", false);xhr.send(' + param_name + ');' + param_name + '=xhr.responseText;'# 如果输入的数据类型不是json或string,输出错误提示并继续循环else:print(">您的数据类型输入有误")returnTrue# 输出生成的payloadprint('payload生成完毕:n' + base_payload)# 打印分隔符,结束当前输入提示print("============================================================================================")

这个get_payload就是根据你输入的参数名称来生成要插入到浏览器的XHR请求代码,如何修改本地浏览器的JS代码这个大家可以通过本地替换、中间人替换的功能来进行payload的插入,这里就不再赘述大家可以自己找找,教程还是蛮多的。

我们就拿一个string类型的变量名称为password的生成的payload来解释:

var xhr = new XMLHttpRequest();xhr.open("post", "[http://127.0.0.1:28080/REQUEST",](http://127.0.0.1:28080/REQUEST",) false);xhr.send(password);password=xhr.responseText;

这段代码创建一个同步的 HTTP POST 请求,将 password 的值作为请求体发送到 http://127.0.0.1:28080/REQUEST,然后将服务器的响应内容赋值回 password

其实这段插入到浏览器的代码的目的就是在网站JS逻辑中的password值加密前还是明文值的时候替换为28080端口返回的值(也就是我们通过burp修改后的值),所以最后的 password=xhr.responseText 其实就是对我们password变量值的覆盖。至于是如何将原始的明文值发送到burp的以及如何获取我们通过burp修改后的值,请大家接着往后阅读。

接着后面创建了t1和t两个线程对象,且通过 daemon = True 都将这两个线程对象设置为守护线程( 守护线程是指当主程序退出时,守护线程会自动结束,即使它们还在运行)。然后通过join()方法来确保程序持续运行这两个线程的任务:

t1 = Thread(target=echo_forward_server_thread)t = Thread(target=echo_server_thread)# 将俩子线程设置为守护线程后启动线程t.daemon = Truet.start()t1.daemon = Truet1.start()print(">准备就绪 请启动Burp,端口:8080")# 通过join方法阻塞主线程直到子线程执行完毕for t in [t, t1]:    t.join()

3.3 转发服务器的设置(28080端口)

我们接下来看 t1 = Thread(target=echo_forward_server_thread) 这个线程对象启动的任务。

defecho_forward_server_thread():print('>开始监听转发服务器,端口:{}'.format(FORWORD_PORT))    server = HTTPServer(('0.0.0.0', FORWORD_PORT), ForwardRequestHandler)    server.serve_forever()

其实这个任务就是先通过HTTPServer定义了一个FORWORD_PORT(28080)端口服务,并且通过serve_forever方法保证服务的持续运行,并且这里定义服务的时候使用了自定义的请求处理器类ForwardRequestHandler来自定义经过28080端口的响应行为

ForwardRequestHandler的代码如下:

classForwardRequestHandler(BaseHTTPRequestHandler):#POST请求方法的响应逻辑defdo_POST(self):# 获取发送给28080端口的请求的请求头中的content-length        content_length = int(self.headers.get('content-length'0))# 设置发送响应状态码200self.send_response(200)# 设置允许跨域访问self.send_header('Access-Control-Allow-Origin','*')# 完成响应头的发送,接下来是发送响应体self.end_headers()# 读取请求体数据        data = self.rfile.read(content_length)# 如果请求路径为/REQUESTifstr(self.path) == "/REQUEST":# 发送请求到http://127.0.0.1:38080/,并设置代理为http://127.0.0.1:8080,将请求体数据作为参数            r = requests.request('REQUEST''http://127.0.0.1:{}/'.format(ECHO_PORT),                                 proxies={'http''http://127.0.0.1:{}'.format(BURP_PORT)},                                 data=data)# 获取响应数据            new_data = r.text# 将响应数据写入响应体self.wfile.write(new_data.encode('utf8'))else:try:# 发送请求到http://127.0.0.1:38080/,并设置代理为http://127.0.0.1:8080,将请求体数据作为参数                r = requests.request('RESPONSE''http://127.0.0.1:{}/'.format(ECHO_PORT),                                     proxies={'http''http://127.0.0.1:{}'.format(BURP_PORT)},                                     data=data)# 获取响应数据                new_data = r.text# 将响应数据写入响应体self.wfile.write(new_data.encode('utf8'))except:# 如果发生异常,将请求体数据写入响应体self.wfile.write(data)

这个ForwardRequestHandler类是BaseHTTPRequestHandler的集成,并且重写了do_POST方法来自定义post请求的处理和响应逻辑。

这里的ForwardRequestHandler主要是设置了浏览器通过XHR发送请求到28080端口的响应逻辑,28080端口的响应的值又是通过以下请求的响应获取的:

r = requests.request('REQUEST或者RESPONSE''http://127.0.0.1:{}/'.format(ECHO_PORT),                         proxies={'http''http://127.0.0.1:{}'.format(BURP_PORT)},                         data=data)

(这个REQUEST和RESPONSE是我们在程序运行的时候需要选择的两个请求类型,这个两个请求类型是作者自定义的,主要用于我们做明文参数类型的区分,后续在38080端口大家可以看到这两个请求类型的处理逻辑。)

这段代码的作用是将最开始的原始的明文值发送到38080端口的服务,并且设置了BURP的8080的端口作为代理服务,设置代理就是让请求可以途径8080端口,这就是我们为什么能在burp看到明文的原因。这时候大家肯会有疑问,这个38080端口服务又是做什么的呢??大家别急,接着往后看。

3.4 响应服务器/镜像服务器 的设置(38080端口)

我们接下来再看 t = Thread(target=echo_server_thread) 这个线程对象启动的任务:

echo_server_thread函数的代码如下:

defecho_server_thread():print('>开始监听镜像服务器,端口:{}'.format(ECHO_PORT))    server = HTTPServer(('0.0.0.0', ECHO_PORT), RequestHandler)    server.serve_forever()

这个线程的任务同理,也是开启一个38080端口作为服务来处理请求和响应,所以我们直接看RequestHandler这个自定义的处理器类的逻辑:

# 定义一个处理请求的类,继承自BaseHTTPRequestHandler,用于处理HTTP请求classRequestHandler(BaseHTTPRequestHandler):# 重写do_REQUEST方法来处理REQUEST请求defdo_REQUEST(self):# 获取请求体的内容长度        content_length = int(self.headers.get('content-length'0))# 设置响应状态码为200(请求成功)self.send_response(200)# 结束响应头的发送self.end_headers()# 将请求体的内容作为响应    返回给客户端self.wfile.write(self.rfile.read(content_length))# 将do_RESPONSE方法指向do_REQUEST方法,用于处理RESPONSE请求    do_RESPONSE = do_REQUEST

这个38080端口的响应逻辑很简单,就是把发送给38080请求的请求体原封不动的作为响应再发送回去,也就是先发送到burp再到28080,最后通过28080的响应再将修改后的明文值传给浏览器,这时候再看一下这个图是不是就明白啦?同时无论大家在使用的时候选择的是REQUEST还是RESPONSE类型,38080服务对请求方法的处理逻辑都是一致的。

JS-Forward 源码剖析与工作机制解析

常见问题

这时候大家可能会有一个问题那就是为什么需要开启两个端口服务来做这件事呢?为什么不把burp放到前面来呢,就像下面这样,这样就只需要一个端口服务了呀:

JS-Forward 源码剖析与工作机制解析

首先大家要明白的是,这个浏览器发出的请求只是携带了我们的明文参数,它并不是一个正确完整的发送到网站自身服务器的请求,其次如果是上面这种情况的话,就需要_JS代码发送途径burp代理的请求到38080,但是在浏览器环境当中,JS代码是不能直接设置请求代理这个行为的,所以大部分情况都需要一个服务器做中转代理服务器来实现,所以我们需要一个28080端口服务来充当这个请求转发服务器的作用。

原文始发于微信公众号(天欣安全实验室):JS-Forward 源码剖析与工作机制解析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月7日13:31:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JS-Forward 源码剖析与工作机制解析https://cn-sec.com/archives/3787922.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息