尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
CTF实战:JWT算法混淆与Python pickle反序列化漏洞解析
1. 从两道题看Web安全里的两个经典坑一天一道CTF做到第41天说实话已经进入了一个比较微妙的阶段——简单题刷完了纯靠工具扫的题也差不多见底了剩下的基本都是需要你真正理解某个机制才能绕过去的题目。今天碰到的两道题恰好代表了Web安全里两个非常经典的方向一个是JWTJSON Web Token的算法混淆与密钥爆破另一个是Python pickle反序列化漏洞的利用。这两个东西在实际渗透测试和代码审计中出现频率极高而且一旦理解透了你会发现它们的利用思路其实非常“优雅”。这篇文章适合已经有一点CTF基础、知道Burp怎么抓包改包、能看懂Python基本语法但还没系统接触过JWT安全和反序列化漏洞的读者。如果你完全没碰过CTF建议先去做几道SQL注入和文件包含的题再来不然可能会觉得跳跃太大。我会把每道题的完整思路、踩过的坑、以及我实际测试时的一些心得都写出来尽量让你看完之后能自己复现一遍。JWT和pickle这两个东西一个是认证机制里的常见实现一个是Python生态里绕不开的序列化方案它们的共同点是开发者觉得用起来很方便但安全边界非常容易被忽视。JWT如果密钥弱或者算法校验不严攻击者可以直接伪造任意身份pickle如果对不可信数据做反序列化基本等于把代码执行权限拱手让人。下面我分两大块来讲每块都会从原理到实操完整走一遍。2. JWT算法混淆与密钥爆破实战2.1 JWT的基本结构与你需要关注的三个部分JWT的全称是JSON Web Token本质上就是一个字符串由三部分组成用点号分隔Header.Payload.Signature。Header是一个Base64Url编码的JSON通常包含算法类型alg和令牌类型typPayload也是Base64Url编码的JSON里面放的是实际传递的数据比如用户ID、角色、过期时间等Signature是对前两部分的签名用来保证令牌没有被篡改。很多人第一次看到JWT会觉得它像加密过的其实不是——Header和Payload就是明文Base64编码任何人拿到Token都可以直接解码看到内容。JWT的安全性完全依赖于签名部分。如果签名算法被绕过或者密钥被猜出来整个认证体系就形同虚设。我在做这道题的时候第一步就是拿Burp抓包找到请求头里的Authorization字段把Token复制出来。然后用一个在线的JWT解码工具或者直接手写Python脚本解码看看Payload里有什么。这道题的Payload里有一个role字段值是user还有一个username字段。很明显如果把role改成admin就有可能拿到管理员权限。但问题是签名。你改了Payload签名就对不上了服务器会拒绝。所以接下来的核心就是怎么让服务器接受一个被篡改过的Payload。2.2 算法混淆攻击把RS256降级成HS256这道题的第一个突破口是算法混淆。JWT支持多种签名算法常见的有HS256HMAC SHA-256对称密钥和RS256RSA SHA-256非对称密钥。HS256用同一个密钥签名和验证RS256用私钥签名、公钥验证。算法混淆攻击的核心逻辑是这样的服务器原本用RS256验证时应该用RSA公钥来验签。但如果服务器代码没有严格限定算法类型攻击者可以把Header里的alg改成HS256然后用RSA公钥的内容作为HMAC的密钥来签名。服务器一看alg是HS256就傻乎乎地用那个“公钥”当HMAC密钥去验证结果就通过了。这个攻击成立的前提是服务器信任了客户端传来的alg字段并且没有做算法白名单校验。在实际代码中很多JWT库的默认行为就是读取Header里的alg来决定验证方式如果开发者没有显式指定算法就会中招。我实际操作的时候先要拿到服务器的RSA公钥。有些题目会直接把公钥放在某个路径下比如/jwks.json或者/publickey.pem。这道题的公钥藏在响应头的一个自定义字段里Base64编码过的。拿到公钥之后用Python的PyJWT库来构造恶意Tokenimport jwt public_key open(public.pem, rb).read() payload { username: admin, role: admin, iat: 1700000000 } token jwt.encode(payload, public_key, algorithmHS256) print(token)注意这里有个坑PyJWT的新版本对算法混淆有防护如果你直接用公钥做HMAC密钥它可能会报错。我试的时候用的是PyJWT 1.7.1版本这个版本没有防护可以直接生成。如果你用的是新版本可以手动构造Header和Payload然后自己算HMAC签名不依赖库。构造出来的Token发过去如果服务器返回了管理员权限的响应就说明算法混淆成功了。这道题确实就是这么过的。注意算法混淆攻击的关键在于服务器是否严格校验了alg字段。如果服务器代码里写死了algorithms[RS256]这个攻击就不成立。所以拿到一道JWT题先试算法混淆不行再想别的路子。2.3 弱密钥爆破当HS256的密钥太简单时如果算法混淆走不通第二招就是爆破HS256的密钥。HS256是对称加密签名和验证用的是同一个密钥。如果这个密钥设得太弱比如就是secret、123456、password这种那用字典跑一遍就能猜出来。我常用的工具是hashcat配合一个专门的JWT字典。命令大概长这样hashcat -m 16500 -a 0 token.txt jwt_wordlist.txt其中-m 16500是hashcat里JWT的哈希模式token.txt里放完整的JWT字符串jwt_wordlist.txt是字典文件。hashcat会自动提取签名部分进行爆破。如果字典够好几秒钟就能出结果。除了hashcat还有一个专门针对JWT的爆破工具叫jwt_tool用起来更直观python3 jwt_tool.py token -C -d jwt_wordlist.txt-C表示crack模式-d指定字典。这个工具还会自动尝试一些常见的弱密钥比如空密钥、none算法等。这道题我两种方法都试了。算法混淆直接成功了所以爆破只是作为备用验证。但我在本地搭环境测试的时候发现很多CTF题目的JWT密钥就是从rockyou.txt的前1000行里取的所以准备一个精简但高质量的字典非常重要。我自己的字典里包含了常见的英文单词、数字组合、以及一些CTF中经常出现的字符串比如ctf、flag、admin、secret这些。2.4 none算法攻击一个已经过时但偶尔还能碰到的漏洞还有一种更古老的攻击方式叫none算法攻击。JWT规范里允许alg设为none表示不签名。如果服务器没有禁用这个算法攻击者可以把alg改成none然后把签名部分留空服务器就会直接接受这个Token。这个漏洞在早期的JWT库中很常见现在大部分库都默认禁用了none算法。但在一些老系统或者自己手写的JWT实现中仍然可能碰到。我在这道题里试了一下服务器直接返回了签名验证失败说明none算法已经被禁用了。不过我还是建议你在遇到JWT题的时候把none算法作为第一个尝试因为操作最简单把Header改成{alg:none,typ:JWT}Payload改成你想要的内容签名部分直接删掉或者留一个空字符串然后发过去。如果服务器接受了那就是秒杀。2.5 JWT安全测试的完整检查清单做了这么多JWT题我总结了一个检查清单拿到Token之后按顺序试一遍基本能覆盖大部分漏洞检查项操作方法成功标志none算法改alg为none删除签名服务器接受Token算法混淆RS256改HS256用公钥签名服务器接受Token弱密钥爆破hashcat或jwt_tool跑字典爆破出密钥kid注入修改kid字段为文件路径或SQL注入服务器读取指定文件或报错jku/x5u篡改修改jku指向自己的JWKS服务器从攻击者服务器获取密钥过期时间篡改修改exp字段为未来时间服务器接受过期Token这个清单不是万能的但覆盖了CTF中90%以上的JWT考点。我个人的习惯是先试none再试算法混淆然后爆破最后才考虑kid注入和jku篡改这些稍微复杂一点的。3. Python pickle反序列化漏洞深度解析3.1 pickle是什么为什么它这么危险pickle是Python标准库里的一个模块用来把Python对象序列化成字节流也可以把字节流反序列化回Python对象。它的设计目标是让Python对象能够方便地存储和传输比如你可以把一个字典、一个列表、甚至一个自定义类的实例直接pickle成二进制数据存到文件里或者通过网络发出去接收方unpickle之后就能得到原来的对象。听起来很方便但问题在于pickle的反序列化过程可以执行任意代码。这不是bug而是设计如此。pickle的文档里明确写了“永远不要unpickle不可信的数据”但很多开发者要么没看到这句话要么觉得自己的数据来源是可信的结果就出了大问题。pickle为什么能执行代码因为pickle的字节流实际上是一系列操作码opcode这些操作码告诉Python解释器如何重建对象。其中有一个操作码叫REDUCE它允许调用一个可调用对象callable并传入参数。攻击者可以构造一个字节流让REDUCE调用os.system或者subprocess.check_output之类的函数从而执行任意命令。我在这道CTF题里拿到的入口是一个看起来像Base64字符串的参数解码之后是一串二进制数据。用pickletools分析了一下发现里面确实有REDUCE操作码。这道题的意图很明显构造一个恶意的pickle payload让服务器反序列化时执行命令读取flag文件。3.2 手工构造pickle payload的完整过程构造pickle payload有两种方式一种是直接用Python的pickle模块序列化一个恶意对象另一种是手工编写pickle字节码。第一种方式更简单但需要你定义一个类并在类的__reduce__方法里指定要执行的命令。第二种方式更灵活可以精确控制每一个操作码。先讲第一种方式。假设我们要执行cat /flag可以这样写import pickle import os class Exploit: def __reduce__(self): return (os.system, (cat /flag,)) payload pickle.dumps(Exploit()) print(payload)这段代码定义了一个Exploit类实现了__reduce__方法。__reduce__方法告诉pickle在反序列化时应该调用什么函数、传什么参数。这里返回的是(os.system, (cat /flag,))意味着反序列化时会执行os.system(cat /flag)。把生成的payload用Base64编码然后作为参数发给服务器如果服务器直接pickle.loads了这个参数命令就会执行。但实际CTF题往往不会这么简单。有些题目会对payload做过滤比如禁止某些操作码或者限制反序列化的类。这时候就需要手工构造字节码绕过过滤。手工构造pickle字节码需要了解常用的操作码。我列几个最核心的操作码含义用途cGLOBAL压入一个全局对象如os.system(MARK压入一个标记用于构建元组或列表tTUPLE从标记处构建元组RREDUCE调用可调用对象.STOP结束反序列化SSTRING压入一个字符串VUNICODE压入一个Unicode字符串一个最简单的执行命令的payload字节码大概是这样的cos system (Scat /flag tR.逐行解释c操作码后面跟模块名和函数名这里压入os.systemS压入字符串cat /flag(压入一个标记t从标记处构建元组得到(cat /flag,)R调用os.system并传入元组作为参数.结束。用Python的pickle.loads直接加载这段字节码就会执行命令。我实测的时候把这段字节码用Base64编码后发给服务器服务器返回了flag的内容。3.3 绕过常见过滤的几种思路CTF题目不会让你这么轻松就拿到flag。常见的过滤手段包括禁用os模块、禁用system函数、过滤关键词如cat、flag、system等。下面是我遇到过的几种过滤和对应的绕过方法。第一种禁用os.system。可以用subprocess.check_output代替或者用platform.popen。如果这些都被禁了还可以用builtins.exec来执行Python代码比如exec(import os; os.system(cat /flag))。第二种过滤关键词。比如过滤了cat可以用c\at或者cat拼接或者用通配符c?t、c*。如果过滤了flag可以用fla?或者f*。在Linux shell里这些通配符都会被展开。第三种禁用REDUCE操作码。有些题目会检查payload里是否包含R操作码。这时候可以用其他方式执行代码比如利用__setstate__或者__reduce_ex__。不过这种绕过比较复杂需要对pickle的协议有深入理解。第四种限制反序列化的类。有些题目会用一个白名单来限制可以反序列化的类。这时候需要找到白名单里的某个类看它的__reduce__或者__setstate__方法是否可以被利用。这种题难度较高通常需要结合具体的类实现来分析。我在这道题里遇到的过滤是禁用了os模块。我的绕过方法是使用subprocess模块import pickle import subprocess class Exploit: def __reduce__(self): return (subprocess.check_output, ([cat, /flag],)) payload pickle.dumps(Exploit())subprocess.check_output的返回值是命令的输出但pickle反序列化时不会打印返回值所以这个payload执行了命令但看不到结果。要看到结果需要让命令的输出以某种方式返回给客户端比如写入一个Web可访问的文件或者通过DNS外带。这道题的环境里我把flag写到了/tmp目录下然后通过另一个文件读取漏洞拿到了内容。3.4 pickle反序列化的实际利用场景pickle反序列化漏洞不仅仅出现在CTF里在实际的Web应用中也时有发生。常见的场景包括Session存储有些框架把session数据pickle之后存在客户端Cookie里如果服务端直接unpickle Cookie内容攻击者就可以伪造session执行代码。缓存系统Redis、Memcached等缓存里存储的pickle数据如果缓存被污染或者攻击者能写入缓存反序列化时就会触发漏洞。消息队列RabbitMQ、Celery等消息队列中传输的pickle数据如果消息来源不可信同样存在风险。机器学习模型PyTorch的模型文件本质上就是pickle加载不可信的模型文件等于执行任意代码。我在实际工作中遇到过一个是Django应用把session存在Redis里序列化方式用的pickle。攻击者通过SSRF漏洞写入了恶意的Redis键值然后触发session读取最终实现了远程代码执行。这个链条比较长但每一步都不复杂核心就是pickle反序列化这个点。提示如果你在代码审计中看到pickle.loads、pickle.load、cPickle.loads这些函数并且输入参数来自用户可控的地方基本可以判定存在反序列化漏洞。修复方法很简单换成JSON序列化或者用HMAC对数据进行签名验证。3.5 pickle利用的常见问题与排查在实际构造pickle payload的过程中我踩过不少坑这里整理几个最常见的问题和解决方法。问题一payload发送后服务器没有执行命令。可能的原因有很多payload没有正确编码、服务器没有调用pickle.loads、命令执行了但没有回显。排查方法是先用一个简单的命令比如sleep 5看服务器响应时间是否变长。如果变长了说明命令执行了只是没有回显。这时候需要想办法外带数据。问题二payload里的字符串包含特殊字符导致解析失败。pickle字节码对字符串的长度和编码有严格要求。如果字符串里有换行符或者非ASCII字符需要特别处理。我通常用pickle.dumps生成payload而不是手工拼接这样可以避免大部分编码问题。问题三服务器使用了pickle的更高协议版本。pickle有多个协议版本从0到5。不同版本的字节码格式不同。如果服务器用的是协议2而你用协议0构造payload可能无法正确反序列化。解决方法是在pickle.dumps里指定protocol参数或者用pickletools分析服务器返回的数据来确定协议版本。问题四命令执行了但找不到flag文件。这时候需要先执行ls /和find / -name *flag*来定位flag文件的位置。有些题目会把flag放在环境变量里这时候需要用echo $FLAG或者env来读取。下面是一个我常用的pickle payload生成脚本支持指定命令和协议版本import pickle import base64 import sys class Exploit: def __reduce__(self): return (eval, (__import__(os).popen(%s).read() % sys.argv[1],)) payload pickle.dumps(Exploit(), protocol0) print(base64.b64encode(payload).decode())这个脚本用eval来执行命令好处是可以直接拿到命令输出的字符串。但eval在很多环境里被禁用了所以实际使用时需要根据题目环境调整。4. 两道题背后的通用方法论4.1 拿到一道Web题时的标准排查流程做了40多天的CTF我逐渐形成了一套固定的排查流程。拿到一道Web题先看URL和参数判断大概是什么类型的漏洞。然后按以下顺序尝试查看源代码和响应头很多题目的提示藏在HTML注释、响应头的自定义字段、或者robots.txt里。尝试常见入口SQL注入、文件包含、命令注入、SSRF、XXE这些是最高频的考点。检查认证机制如果有登录功能看session和Token是怎么生成的。JWT题通常在这一步能发现线索。检查序列化数据如果参数值看起来像Base64或者二进制解码看看是不是pickle、PHP serialize、Java serialize等格式。尝试绕过过滤如果发现有WAF或者关键词过滤尝试编码绕过、大小写绕过、拼接绕过等。这套流程不是死的但能帮你在没有思路的时候快速定位方向。我个人的经验是CTF题目通常不会考太偏门的知识点大部分题目都是围绕几个核心漏洞类型出的。把JWT和pickle这两个点吃透至少能解决20%的Web题。4.2 工具链的搭建与使用建议工欲善其事必先利其器。我平时做CTF用的工具不多但每一个都用到极致Burp Suite抓包改包必备社区版免费功能足够。jwt_toolJWT专用工具支持爆破、算法混淆、kid注入等多种攻击。pickletoolsPython自带用来分析pickle字节码。hashcat爆破各种哈希包括JWT的HMAC签名。CyberChef在线编解码工具支持Base64、Hex、URL编码等非常方便。这些工具不需要全部精通但至少要知道每个工具能做什么遇到问题时知道该用哪个。我见过一些新手遇到JWT题不知道有jwt_tool这个工具手工去算HMAC浪费了大量时间。工具的价值在于让你把精力集中在思路上而不是重复劳动上。4.3 从CTF到实际工作的能力迁移很多人觉得CTF和实际工作脱节我的看法恰恰相反。CTF里学到的漏洞原理和利用思路在实际渗透测试和代码审计中直接就能用。JWT的算法混淆、pickle的反序列化、SQL注入的绕过技巧这些在实际项目中都是真实存在的风险点。区别在于实际工作中的系统更复杂漏洞可能藏在多层调用之后需要你有更强的代码阅读能力和调试能力。但核心的漏洞原理是一样的。我在实际项目中遇到过一个JWT弱密钥的问题用的就是CTF里学到的爆破方法几分钟就拿到了管理员权限。客户当时很惊讶觉得怎么可能这么快其实只是因为他们的密钥就是companyname2023字典里刚好有。pickle反序列化在实际工作中相对少见但一旦遇到就是高危漏洞。我建议你在CTF里把pickle的利用方式练熟包括手工构造字节码、绕过过滤、外带数据等技巧。这些技能在关键时刻能派上大用场。5. 我踩过的坑和给你的建议5.1 JWT部分的三个坑第一个坑PyJWT版本问题。我一开始用最新版的PyJWT做算法混淆结果一直报错说密钥格式不对。后来查了文档才发现PyJWT 2.x版本增加了算法混淆的防护不允许用RSA公钥做HMAC密钥。换成1.7.1版本之后问题解决。所以如果你在做算法混淆时遇到莫名其妙的错误先检查库的版本。第二个坑Base64Url编码和标准Base64的区别。JWT用的是Base64Url编码把标准Base64里的和/替换成了-和_并且去掉了末尾的。我手工构造Token的时候一开始忘了做这个替换导致服务器解析失败。后来写了一个小函数专门处理这个转换就再也没出过问题。第三个坑时间戳问题。JWT的Payload里通常有iat签发时间和exp过期时间。如果你构造的Token里exp是过去的时间服务器会直接拒绝根本不走到签名验证那一步。我一开始没注意这个改了半天签名都没用后来把exp改成未来时间就好了。5.2 pickle部分的两个坑第一个坑协议版本不匹配。我用pickle.dumps生成payload时默认用的是协议4或5但服务器上的Python版本比较老只支持到协议2。结果payload发过去之后服务器直接报错。解决方法是在pickle.dumps里显式指定protocol0协议0是ASCII格式的兼容性最好。第二个坑命令回显问题。我构造的payload执行了命令但服务器没有任何返回。一开始我以为payload没生效后来用sleep命令测试发现确实执行了只是os.system的返回值不会出现在HTTP响应里。解决方法是用subprocess.check_output获取命令输出然后通过raise Exception把输出抛出来这样错误信息里就会包含命令结果。或者把结果写到Web目录下的文件里再通过浏览器访问。5.3 给正在刷CTF的朋友的几句实话刷CTF最大的误区是只看WriteUp不动手。我见过很多人看别人的WriteUp觉得很简单自己一上手就卡住。原因很简单看的时候你是在被动接受信息动手的时候你是在主动解决问题两者的难度完全不一样。我的建议是每道题至少自己先尝试30分钟实在没思路再看提示看完提示再自己复现一遍。另外不要追求刷题数量。一天一道题但每道题都吃透比一天十道题但都是抄WriteUp强得多。我做到第41天前30天基本都是在打基础最近这10天才开始接触JWT、pickle、SSTI这些稍微高级一点的考点。基础打牢了后面的路会越走越顺。最后说一个我自己的习惯每做完一道题我会写一段简短的笔记记录这道题的核心考点、我踩的坑、以及如果下次遇到类似的题应该怎么入手。这个习惯让我在遇到重复考点时能快速反应不用从头开始想。笔记不用写得多正式几句话就行关键是记录你自己的思考过程而不是抄别人的答案。
RELATED

相关推荐

Diagram Design:从信息设计到视觉编码的完整方法论

Diagram Design:从信息设计到视觉编码的完整方法论

1. 从"diagram-design"这个标题里能读出什么第一次看到"diagram-design"这个标题,我脑子里蹦出来的第一个判断是:这不是一个单纯的画图工具介绍,而是一个关于"如何把复杂信息用图形讲清楚"的方法论命题。很多人…

📅 2026/10/20 23:03:47
物联网毕设选题推荐:五个易上手但拿得出手的方向

物联网毕设选题推荐:五个易上手但拿得出手的方向

1. 选题背后的门道:为什么“易上手”反而更难选每年到这个时间点,总有一批物联网专业的同学开始焦虑毕业设计的事情。我在这个行当摸爬滚打了十来年,也帮不少同学审过选题、救过进度,发现一个很有意思的现象:大家普遍觉…

📅 2026/10/20 23:03:47
select多路复用实战:从阻塞到事件驱动,手写并发服务器

select多路复用实战:从阻塞到事件驱动,手写并发服务器

1. 从阻塞到多路复用:为什么select是绕不开的一课刚接触网络编程那会儿,我写过一个最简单的TCP回声服务器:主线程accept一个连接,然后read、处理、write,再回到accept。单客户端测试一切正常,两个客户端一连…

📅 2026/10/20 23:03:47
MORE NEWS

更多资讯

📰

Unet-SAM提示框融合模型:医生画框即得精准息肉分割

简介:本资源面向医学图像处理方向的深度学习研究者与开发者,聚焦结直肠镜检查中息肉肿瘤的高精度语义分割任务,提供一套融合Selective Attention Module(SAM)提示机制的Unet改进方案。资源包含完整可运行代码、标注数据…

📰

aclnnReal 算子新手指南:快速掌握复数取实部算子的完整调用流程

aclnnReal 算子新手指南:快速掌握复数取实部算子的完整调用流程 【免费下载链接】ops-math 本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。 项目地址: https://gitcode.com/cann/ops-math 在 ops-math 算子库中,a…

📰

Python酒店推荐系统:Flask+SQLite实现可解释、可调试的全链路推荐

简介:本资源是一套面向本科毕业设计与课程设计的Python全栈酒店推荐系统,适用于计算机专业学生完成Web开发类实践项目,解决酒店在线预订与后台管理一体化建模需求。压缩包共597个文件,33.83MB,涵盖63个JavaScript前端交…

📰

飞行力学Matlab仿真全流程:配平、线性化与模态分析实战

简介:面向北京理工大学2025春季学期飞行力学课程的研究型学习大作业,资源提供了一套可直接运行的完整项目方案,案例数据无需额外配置即可复现。内容覆盖导弹六自由度运动建模与关键参数验证、不同导引方法的导引弹道运动学分析等核心专题&…

📰

SCI365深度解析:学术信息聚合工作台,而非论文生成器

上周组会上,A同学举着手机问我:“师兄,SCI365是不是那种输入一个标题,就能自动生成整篇论文的工具?”我第一反应是摇头,但细一想,这真不怪他。短视频里但凡提到科研效率工具,十个有八…

📰

Deepagents自动化测试实战:从断言设计到稳定性体系建设

做过AI Agent相关开发的朋友应该都有同感:写一个能跑通的代理不难,难的是让它稳定地跑完一百遍。LLM天然带有随机性,同样的输入,今天输出这个结果,明天就可能换个路径,哪怕最终答案都对,中间调用…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬