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变量的变化。
- 在Network面板,找到登录请求的
- 检查是否在框架内:如果系统使用了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逆向分析方法论:从抓包观察、关键词搜索、断点调试、逻辑分析到代码复现和验证。这套方法,稍加变通,就能应用到更多、更复杂的场景中去。记住,耐心和细致是逆向分析中最宝贵的品质,每一个细节都可能成为突破的关键。