以Web代理劫持会话绕过并破解网页版软件USB-Key加密狗方案

!本文可能 超过1年没有更新,今后内容也许不会被维护或者支持,部分内容可能具有时效性,涉及技术细节或者软件使用方面,本人不保证相应的兼容和可操作性。

单位某信息管理系统,需要加密狗才能运行,因为加密狗只有一个,该系统又需要维护各部门台账信息,遂思考在不增加加密狗的情况下能否将该系统分发下去,已知该系统采用Java Web方式编写,虽然有桌面应用的样子,但实质上是Web应用,很容易找到了Java Web服务器和实际的访问URL,在防火墙端口开放访问,并在路由器上做好端口映射,其他电脑访问正常,本来以为万事大吉,哪知道在登录的时候客户端提示没有加密狗无法登录。

审阅了登录页面的源代码,发现其采用ActiveX控件方式读取加密狗并设置登录信息以便于提交服务器,客户端没有加密狗,这个验证肯定也无法通过,原来我一直以为这个软件的加密狗是用于服务器端的,没想到客户端网页也采用了加密狗保护机制。

通常意义上加密狗是一种软件保护机制,主要防止软件被盗版,其通过硬件的方式保护核心的算法(比如关键公式),在软件需要的时候由硬件进行数据处理以完成整个软件流程,那么可以知道其登录信息的加密方式是存储在加密狗里面的,如果要登录成功又必须需要加密后的登录凭证。

因为该软件项目规模较大,反编译修改软件较为复杂,而且领导明确要求不可以改动原软件,所以只有另行他法。

已知我们现在有一正常工作的加密狗,为什么不直接借助该加密狗进行验证,再将验证数据代理提交呢?这里衍生出第一套方案,采用在有加密狗的服务器上私设反向代理Web服务器,并编写简单的登录页面,当用户使用该页面登录的时候我们获取到用户名和密码,再使用加密狗进行运算,最后将结果提交给真实的Java Web服务器,最后获取到Session会话的Cookie,再一次的将该Cookie使用Set-Cookie返回给用户,这样客户端用户相当于劫持了原来私设Web服务器与真实Java Web服务器的会话,然后成功登录。

反向代理劫持会话破解加密狗

这种方式要成功有个关键,那就是处于同一域名下(跨域名如果要成功主要要考虑Cookie跨站等因素),最好的状态是直接借助真实的Java Web服务器,设置新的登录页面,只不过将加密狗验证放在服务器端完成,这样可行性更高,也避免Cookie受到反XSS机制的干扰。

提到自己搭建Web服务器可能大家会想到使用IIS、Apache等,其实这里并不需要这么麻烦,因为我只需要维护一小部分用户访问,所以使用Python的SimpleHTTPServer库即可,对于向Web Server发出请求则用到了requests库(需要安装),一个简单实现的例子如下:

# -*- coding: utf-8 -*-
 
import cgi
import requests
import Cookie
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
 
class ProxyRobot(object):
 
    urls = {
        "login" : "/login.jsp",
        "auth"  : "/login.jsp",
    }
 
    base_url = "//wangye.org/"
 
    headers = {
        "User-Agent"       : "Mozilla/5.0 (Windows NT 6.0; WOW64; rv:15.0) Gecko/20090101 Firefox/14.0.1",
        "Accept"           : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Accept-Language"  : "zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3",
        "Accept-Encoding"  : "gzip,deflate",
        "Connection"       : "keep-alive",
        "Referer"          : "//wangye.org/",
    }
 
    def post(self, url_name, data):
        et = self.session.post(ProxyRobot.base_url + ProxyRobot.urls[url_name], data, headers=ProxyRobot.headers)
        return et
 
    def get(self, url_name):
        et = self.session.get(ProxyRobot.base_url + ProxyRobot.urls[url_name], headers=ProxyRobot.headers)
        return et
 
    def __init__(self, username, password):
        self.session = requests.Session()
        self.credentials = {"username" : username, "password" : password}
 
    def _prepareLogin(self):
        request = self.get("login")
        # Modify something
        self.credentials["testcookie"] = 1
        return request.text
 
    def login(self):
        self._prepareLogin()
        payload = {
            "log" : self.credentials["username"],
            "pwd" : self.credentials["password"],
        }
        request = self.post("auth", data=payload)
        print request.text
 
    def getCookies(self):
        C = Cookie.SimpleCookie(self.session.cookies.get_dict())
 
        #cookies = self.session.cookies.get_dict()
        #for k,v in cookies.items():
        #    C[k] = v
 
        return C
 
 
class CustomHTTPRequestHandler(BaseHTTPRequestHandler):
 
    def getPostVars(self):
        ctype, pdict = cgi.parse_header(self.headers.getheader('content-type'))
        if ctype == 'multipart/form-data':
            postvars = cgi.parse_multipart(self.rfile, pdict)
        elif ctype == 'application/x-www-form-urlencoded':
            length = int(self.headers.getheader('content-length'))
            postvars = cgi.parse_qs(self.rfile.read(length), keep_blank_values=1)
        else:
            postvars = {}
        return postvars
 
    def send_page_header(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
 
    def send_redirect_header(self, url):
        self.send_response(302)
        self.send_header("Location", url)
 
    def do_POST(self):
        self.send_page_header()
 
        postvars = self.getPostVars()
        username = postvars.get("log", ['',])[0]
        password = postvars.get("pwd", ['',])[0]
 
        robot = ProxyRobot(username=username, password=password)
        robot.login()
        cookies = robot.getCookies()
 
        self.wfile.write(cookies.output())
 
        self.end_headers()
 
 
    def do_GET(self):
        self.send_page_header()
        self.end_headers()
        self.wfile.write("<html>\r\n<head><title>Login Gateway</title></head><body>\r\n")
        self.wfile.write("""
        <form action="" method="POST">
            Username: <input type="text" name="log" /> <br /><br />
            Password: <input type="password" name="pwd" /> <br /> <br />
            <input type="submit" value="Click to Login" />
        </form>
        """)
        self.wfile.write("\r\n</body></html>")
 
 
class CustomHTTPServer(HTTPServer):
    def __init__(self, host, port):
        HTTPServer.__init__(self, (host, port), CustomHTTPRequestHandler)
 
 
def main():
    server = CustomHTTPServer('127.0.0.1', 8000)
    server.serve_forever()
 
 
if __name__ == '__main__':
    main()

事实上我并没有采用上述的方法,因为不知道是什么原因Cookie经常获取不到,所以这里又提到了第二套方案,Plan B计划则采用稳妥方式,将验证交还给客户端,服务器端只保留加密狗验证功能,即接受客户端提交的用户名和密码由加密狗运算得到加密数据,将数据反馈给客户端,客户端再次持该加密数据提交给真实的Java Web服务器端进行验证,成功后就一路绿灯了,这种方式的稳定性和隐蔽性较高,对于Java Web服务器来说实际上仍然和真实的客户端进行数据交换。

服务器端代理绕过加密狗认证

对于Python代理服务器端调用加密狗的方式可以采用ActiveX,具体参考PyWin32的DispatchDispatchEx相关。相关代码我就不贴出来了,这里有个小插曲,原来的软件不光登录页面采取了加密狗限制,甚至连登录后的页面都要验证加密狗,好在其登录后的页面主要加载了一个Flash(不得不吐槽一下该软件大部分由Flash构成),更奇葩的是其在每一次点击Flash子栏目的时候会发起一个检查加密狗是否正常的请求,要不是领导要求不能破坏软件,我都想直接屏蔽掉这个加密狗检查请求了,事实也证明是可以做到的,最后我把登录后的页面另存了一份修改页到我搭建的Python Web服务器下,然后加密狗数据请求部分再次使用Python调用服务器端加密狗获取,也算维护了整体软件的完整吧,具体的代码我就不贴了,以上思路仅供参考。

若无特别说明,本网站文章均为原创,原则上这些文章不允许转载,但是如果阁下是出于研究学习目的可以转载到阁下的个人博客或者主页,转载遵循创作共同性“署名-非商业性使用-相同方式共享”原则,请转载时注明作者出处谢绝商业性、非署名、采集站、垃圾站或者纯粹为了流量的转载。谢谢合作!

请稍后...

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*