1. 项目概述与背景
最近在整理企业级应用安全审计的案例时,我又翻出了通达OA Office Anywhere 2019这个经典的靶场。很多刚接触JS逆向的朋友,一上来就想搞那些大型电商或社交平台,结果往往被复杂的混淆和反调试机制劝退。其实,从一些成熟但架构相对传统的企业应用入手,是理解前端加密逻辑、培养逆向手感绝佳的路径。通达OA作为国内广泛使用的办公自动化系统,其2019版本的前端登录密码加密,就是一个结构清晰、非常适合新手入门的逆向分析样本。它没有使用极度复杂的混淆,但包含了完整的加密函数定位、算法还原、参数模拟等核心环节,堪称“JS逆向三百例”中的标准示范课。
这个项目要解决的问题很明确:当我们面对一个系统的登录接口,发现提交的密码是一串毫无规律的密文时,我们如何从浏览器出发,一步步追踪到生成这串密文的JavaScript代码,并最终理解其加密原理,甚至能够用Python或其他语言独立复现这个加密过程?这不仅是为了“破解”,在安全测试、数据对接、自动化脚本开发等合规场景下,这种分析能力都至关重要。接下来,我就以通达OA 2019为例,带你完整走一遍这个流程,我会把每个步骤的意图、可能遇到的坑以及我调试时用的“骚操作”都交代清楚。
2. 逆向分析的核心思路与准备工作
2.1 分析目标与工具选型
我们的终极目标是独立实现通达OA 2019登录时密码字段的加密算法。具体表现为,输入明文密码(如“123456”),能输出与浏览器发送请求时完全一致的密文。这需要我们先通过浏览器开发者工具,捕捉到登录请求,观察其数据格式,尤其是密码参数(通常叫
PASSWORD
或
password
)的样子。然后,以此为线索,逆向寻找生成它的JavaScript代码。
工欲善其事,必先利其器。对于这类传统Web应用的分析,我习惯用Chrome DevTools,它足够强大。有几个关键功能会高频使用:
-
Network(网络)面板
:用于捕获登录请求,查看请求载荷(Payload)。重点关注
Form Data
或
Payload
标签页下的内容。
-
Sources(源代码)面板
:用于搜索、查看和调试JavaScript文件。全局搜索(
Ctrl+Shift+F
)是定位关键词的神器。
-
Console(控制台)面板
:用于执行JavaScript代码片段,测试我们还原的加密函数。
为什么不推荐用Fiddler或Burp Suite?它们当然也能抓包,但对于前端JS代码的动态调试和追踪,浏览器自带的DevTools有着无可替代的便捷性。Burp更适合用于拦截、重放和修改HTTP请求,属于另一层面的工具。
注意
:在开始分析前,请确保你有一个合法的、用于学习和测试的通达OA 2019测试环境。所有分析操作应仅限于你自己拥有权限的测试系统,严格遵守法律法规,切勿对任何未授权的系统进行测试。
2.2 初步侦察:网络请求抓取与特征观察
首先,我们打开通达OA 2019的登录页面(通常是类似
/login.html
或
/
的地址)。打开DevTools的Network面板,并勾选“Preserve log”(保留日志),防止页面跳转后请求记录被清空。
在登录框输入一个简单的测试密码,比如
test123
,点击登录。此时Network面板会立刻出现一个新的请求,通常是向
/logincheck.php
或类似接口发起的POST请求。点击这个请求,查看其
Headers
和
Payload
。
以我手头的环境为例,捕获到的请求关键信息如下:
-
请求URL
:
http://your-oa-server/general/logincheck.php
-
请求方法
: POST
-
表单数据 (Form Data)
:
UNAME: admin
PASSWORD: 7c4a8d09ca3762af61e59520943dc26494f8941b
这里,
UNAME
是明文用户名,而
PASSWORD
是一串40位的十六进制字符串。40位长度,这强烈暗示了可能是SHA-1哈希算法的结果(SHA-1输出正好是160位,即40个十六进制字符)。当然,这只是一个假设,需要代码验证。我们的任务就是找到把
test123
变成
7c4a8d09ca3762af61e59520943dc26494f8941b
的那段JavaScript代码。
3. 加密函数定位与关键代码追踪
3.1 基于关键词的全局搜索定位
知道密码参数名是
PASSWORD
,密文是40位十六进制串,这给了我们明确的搜索线索。在DevTools的Sources面板,点击左侧文件列表上方的“{}”图标(格式化代码),然后使用
Ctrl+Shift+F
进行全局搜索。
搜索关键词的选择有技巧:
直接搜索参数名
:搜索
PASSWORD
。但这个词可能在HTML、JS里出现很多次,需要过滤。
搜索可能的加密函数名
:既然怀疑是SHA-1,可以搜索
SHA1
、
sha1
、
encrypt
、
hash
等。
搜索特征常量
:有时算法会用到一些常量,比如SHA-1的初始哈希值
0x67452301
等,但这对新手要求略高。
在通达OA 2019中,最有效的方法是搜索
PASSWORD
。在搜索结果中,我们不是漫无目的地看,而是要找那些
在JS文件里,并且看起来在给
PASSWORD
赋值
的代码行。很快,我们可能会定位到一个类似下面的代码片段(代码已做简化示意):
// 可能在某个 .js 文件中,例如 login.js 或 common.js
function do_login() {
var username = document.getElementById('UNAME').value;
var password = document.getElementById('PASSWORD').value;
// 关键行!这里对password进行了处理
document.getElementById('PASSWORD').value = hex_sha1(password);
// 然后提交表单
document.forms[0].submit();
}
或者,在表单的
onsubmit
事件处理函数中:
<form action="/general/logincheck.php" method="post" onsubmit="return checkForm(this);">
然后去找到
checkForm
函数,里面也会有类似的加密逻辑。
实操心得
:全局搜索时,不要只看一眼就过。点击搜索结果跳转到对应文件后,要
查看上下文
。看看这个
PASSWORD
是在哪里被定义的,是谁修改了它的值。通常,加密操作就发生在这个修改的地方。
3.2 深入加密函数:hex_sha1 的逆向
找到了
hex_sha1(password)
这行代码,就等于找到了加密的核心函数。接下来,我们的目标就是找到
hex_sha1
这个函数的定义。继续使用全局搜索,搜索
function hex_sha1
或
hex_sha1 = function
。
在通达OA 2019中,这个SHA-1的实现通常是直接嵌入在一个公共JS文件里的,比如
/static/js/sha1.js
或者直接写在某个大文件的头部。找到后,你会看到一整套SHA-1算法的JavaScript实现。代码可能长这样:
function hex_sha1(s) {
return binb2hex(core_sha1(str2binb(s), s.length * chrsz));
}
function core_sha1(x, len) { … }
function str2binb(str) { … }
function binb2hex(binarray) { … }
// … 以及其他辅助函数
到了这一步,很多新手会觉得大功告成,直接把这一坨JS代码复制出来就完事了。其实这才是最容易出错的地方。
你不能假设这个
hex_sha1
函数就是标准的、独立的SHA-1。你必须验证两件事:
它是否依赖外部变量或函数?
查看
hex_sha1
函数体内,是否使用了在它外部定义的全局变量(比如
chrsz
、
hexcase
等)。这些变量的值必须一并获取。
它的输入输出是否经过额外处理?
在通达OA的这个案例里,
hex_sha1
看起来就是标准的SHA-1。但在其他系统里,可能会在SHA-1前后进行Base64编码、拼接时间戳、加盐(salt)等操作。一定要在调用
hex_sha1
的上下文中确认,传入的参数是不是纯粹的明文密码,有没有被拼接其他字符串。
验证方法很简单:在Console面板里,直接调用我们找到的
hex_sha1
函数。输入我们测试的密码
test123
,看输出是否与抓包得到的密文
7c4a8d09ca3762af61e59520943dc26494f8941b
一致。
// 在Console中测试
hex_sha1('test123');
// 观察输出是否等于 "7c4a8d09ca3762af61e59520943dc26494f8941b"
如果一致,恭喜,算法还原成功。如果不一致,就要回头仔细检查上下文,看密码在传入
hex_sha1
前是否被修改。
4. 算法还原与Python代码实现
4.1 分析JavaScript加密逻辑
经过上一步的验证,我们确认通达OA 2019的密码加密就是标准的SHA-1哈希,没有额外的盐或混淆。其逻辑链条非常清晰:
hex_sha1
函数。
hex_sha1
函数内部按标准SHA-1算法计算哈希值,并返回40位的十六进制字符串。
PASSWORD
字段,随表单提交。
标准SHA-1算法虽然自己实现起来有点复杂(涉及位运算、循环位移等),但幸运的是,我们几乎不需要自己重写。我们的目标是用Python模拟这个加密过程,而Python的标准库
hashlib
已经提供了成熟且高效的SHA-1实现。
4.2 Python复现加密过程
既然算法是标准SHA-1,Python复现就非常简单了。但这里我想强调一个
非常重要的细节
,也是很多人在跨语言实现加密时容易踩的坑:
字符串的编码(Encoding)
。
JavaScript的字符串通常是UTF-16吗?不完全是。在涉及加密哈希时,尤其是这种比较老派的、自己实现算法的JS代码,它处理字符串的方式往往是基于字符串的字符码(charCode)。而Python的
hashlib
的
update
方法需要接收字节(bytes),而不是字符串(str)。
那么,JS的
hex_sha1('test123')
在计算时,到底相当于对什么样的字节序列进行哈希呢?在绝大多数没有指定特殊编码的Web场景下,可以等价于对
UTF-8编码的字节序列
进行哈希。这是一个非常常见且安全的假设。
因此,我们的Python实现代码如下:
import hashlib
def encrypt_password_oa(password):
"""
模拟通达OA 2019前端密码加密
:param password: 明文密码字符串
:return: 40位小写十六进制SHA-1哈希值
"""
# 1. 将字符串转换为utf-8编码的字节
password_bytes = password.encode('utf-8')
# 2. 创建sha1哈希对象并更新数据
sha1_obj = hashlib.sha1()
sha1_obj.update(password_bytes)
# 3. 获取十六进制哈希值
encrypted_password = sha1_obj.hexdigest()
return encrypted_password
# 测试
if __name__ == '__main__':
plain_password = 'test123'
encrypted = encrypt_password_oa(plain_password)
print(f'明文密码: {plain_password}')
print(f'加密后: {encrypted}')
# 输出应该与浏览器捕获的一致: 7c4a8d09ca3762af61e59520943dc26494f8941b
assert encrypted == '7c4a8d09ca3762af61e59520943dc26494f8941b', '加密结果不匹配!'
为什么一定要用
utf-8
编码?
这是为了确保与前端JavaScript行为一致。虽然JS内部是UTF-16,但其
charCodeAt
等函数在处理ASCII范围内的字符(如字母数字)时,与UTF-8编码的ASCII部分是完全兼容的。而对于SHA-1这种将输入视为字节流的算法,使用UTF-8编码是Web领域的通用实践。如果你不放心,可以在JS控制台和Python中同时对一个包含中文的密码进行加密测试,来验证编码是否一致。
踩坑记录
:我曾经遇到一个系统,它的前端JS在计算哈希前,居然用
escape()
函数对密码进行了编码,这会导致空格变成
%20
,加号变成
%2B
。如果你用Python的
hashlib.sha1(password.encode('utf-8'))
计算,结果肯定对不上。这时就需要在Python里用
urllib.parse.quote
来模拟
escape()
的行为。好在通达OA 2019没有这个“骚操作”。
5. 逆向过程中的常见问题与深度排查技巧
即使是一个简单的SHA-1逆向,在实际操作中也可能遇到各种问题。下面我总结了一个排查清单,覆盖了从入门到进阶可能遇到的坑。
5.1 问题一:搜索不到明显的加密函数调用
现象
:全局搜索
PASSWORD
、
encrypt
、
SHA1
都找不到明显的加密代码行。
排查思路
:
检查表单提交方式
:可能不是通过
onsubmit
,而是通过按钮的
onclick
事件触发了某个JavaScript函数,在这个函数里完成了加密和Ajax提交。搜索
onclick
或按钮的ID/Name。
检查是否被混淆
:虽然通达OA 2019没有,但很多现代应用会对JS进行混淆。代码可能变成
a=b[c](d)
这种形式。这时,搜索
PASSWORD
可能只能找到字符串本身,而找不到调用逻辑。你需要:
-
在Network面板,找到登录请求的
Initiator
(发起者)列,点击它可能直接跳转到发起这个请求的JS代码行。
-
在
do_login
或类似函数入口处打上断点(在Sources面板对应行号点击),然后重新登录,让代码执行暂停,再单步调试(F11),一步步跟踪
password
变量的变化。
检查是否在框架内
:如果系统使用了Vue、React等框架,密码绑定和提交逻辑可能藏在组件和方法里。你需要熟悉框架的调试方式,例如在Vue Devtools中查看组件数据。
5.2 问题二:加密结果与Python实现不一致
现象
:成功找到了JS加密函数,在Console里测试结果也与抓包一致,但用Python实现的代码算出来的结果就是不对。
排查步骤(逐层验证)
:
验证基础用例
:先用一个简单密码,如
123456
,分别在JS Console和Python中计算。如果简单密码都对,复杂密码不对,问题可能出在编码;如果简单密码就不对,问题更根本。
验证编码环节
:这是最常见的坑。在JS加密函数的第一行打上断点,或者手动在函数开始时添加
console.log('输入:', password, '类型:', typeof password);
。看看传入的到底是什么。然后在Python里,在调用
encode
之前,也打印一下字符串。确保两者完全一致(包括不可见字符?)。
验证算法细节
:如果JS用的是自定义的、非标准的哈希算法(比如魔改的SHA-1),那么
hashlib
就不管用了。你需要仔细阅读JS算法代码,理解其每一步操作(填充、循环、位运算等),然后用Python原样实现一遍。这个过程非常繁琐,但也是JS逆向的精髓所在。对于通达OA,我们确认它是标准SHA-1,所以这步跳过。
验证最终输出格式
:JS函数返回的是40位小写十六进制吗?有没有可能是Base64?在Console里捕获加密后的值,观察其长度和字符集。Python的
hexdigest()
输出小写,如果JS输出大写,你需要用
.lower()
转换,或者JS里可能有一个
hexcase
变量控制大小写。
5.3 问题三:加密依赖了动态参数(加盐)
现象
:每次登录,即使密码相同,生成的密文也不同。或者密文长度远超40位。
排查思路
:
观察请求参数
:除了
UNAME
和
PASSWORD
,查看登录请求是否还提交了其他参数,比如
token
、
nonce
、
timestamp
、
salt
等。这些很可能被用于和密码拼接后再加密。
分析加密函数输入
:在JS加密函数处打上断点,不仅查看
password
,还要查看函数内部是否拼接了其他变量。例如,可能是
hex_sha1(password + salt)
或
hex_md5(timestamp + password)
。
追踪动态参数来源
:如果发现了加盐,需要找到这个
salt
或
token
是怎么生成的。通常它会在页面加载时由后端渲染到HTML中(如一个隐藏的
input
标签),或者通过一个前置的Ajax请求获取。你需要用同样的思路,去逆向生成这个动态参数的JS代码。
通达OA 2019的总结
:它属于最简单的情况——无盐、静态、标准哈希。这让我们可以把重点放在理解逆向分析的完整流程上,而不是陷入复杂的算法对抗。
6. 逆向分析的进阶思考与防御视角
通过这个简单的案例,我们走完了一个完整的JS逆向流程。但作为安全研究者或开发者,我们不能只停留在“能逆向”的层面,更要思考背后的安全问题。
6.1 从攻击者视角看,这种加密的弱点
通达OA 2019仅使用前端SHA-1加密密码,存在几个明显弱点:
哈希而非加密
:SHA-1是哈希算法,不可逆,但它是公开的、确定性的。相同的密码永远产生相同的哈希值。
无盐(Salt)
:这是最大的问题。攻击者可以预先计算一个巨大的“彩虹表”,里面包含海量常用密码及其SHA-1哈希值。一旦从数据库或网络流量中拿到这个哈希,直接查表就能瞬间得到明文密码。
7c4a8d09ca3762af61e59520943dc26494f8941b
在彩虹表里一查,立刻就知道是
test123
。
前端加密意义有限
:前端加密无法替代HTTPS。它在传输过程中可以防止密码明文被直接窥视,但如果网络层本身不安全(比如HTTP协议),攻击者依然可以轻松截获请求并重放(Replay Attack),因为密文本身就可以作为登录凭证。前端加密的主要作用是避免密码在传输中因明文而意外泄露(例如被浏览器历史记录、日志无意记录),但无法抵御主动攻击。
6.2 从开发者视角看,如何改进?
如果我们是OA系统的开发者,应该如何设计更安全的密码处理方案?
必须使用HTTPS
:这是底线。在HTTPS加密通道下,即使前端不加密,密码明文传输也是安全的。前端加密变成了锦上添花,而非安全依赖。
后端进行强哈希加盐
:前端可以做一次哈希(像通达OA这样),但
后端在存储前必须再做一次加盐哈希
。盐(Salt)应该是每个用户独立、足够长的随机字符串。推荐使用像
bcrypt、scrypt或Argon2
这样的密码哈希函数,它们设计得计算缓慢,能有效抵抗彩虹表和暴力破解。即使前端哈希值泄露,攻击者也无法直接用其登录(因为后端还会用不同的盐再哈希一次),也极难反向破解出原密码。
引入动态挑战值
:可以考虑引入类似CSRF Token的机制。登录时,后端先返回一个随机数(Nonce),前端将密码哈希后,再与这个Nonce进行某种计算(如HMAC),将结果提交。后端用同样的Nonce进行验证。这样,每次登录的密文都不同,有效防止重放攻击。
避免在前端实现核心加密逻辑
:核心的、带盐的哈希运算应在后端完成。前端可以承担一些轻量的、不涉及密钥的预处理工作。
6.3 逆向分析技能的合规应用
最后,强调一下这项技能的合规用途,这远比“破解”更重要:
-
安全审计与渗透测试(授权范围内)
:帮助企业发现自身系统的安全漏洞,如弱加密、逻辑缺陷等。
-
自动化测试与数据对接
:在需要模拟用户登录进行自动化操作(如数据采集、巡检)时,理解加密流程是编写脚本的前提。
-
第三方系统集成
:当需要与一个已有系统进行数据打通,而对方文档不全时,逆向分析可以帮你弄清楚接口的调用方式。
-
学习与研究
:就像我们这次做的一样,通过分析经典案例,深入理解Web安全机制、加密算法应用和前后端交互逻辑。
通过“通达OA 2019密码加密”这个麻雀虽小五脏俱全的案例,我希望你掌握的不仅仅是一个SHA-1的还原,而是
一套通用的JS逆向分析方法论
:从抓包观察、关键词搜索、断点调试、逻辑分析到代码复现和验证。这套方法,稍加变通,就能应用到更多、更复杂的场景中去。记住,耐心和细致是逆向分析中最宝贵的品质,每一个细节都可能成为突破的关键。
