最近看到了一个国外高中生的CTF比赛,翻了一下往年的例题,发现有一道关于jwt session伪造的题比较有意思,记录一下

题目简介中给出了我们题目的地址和后端处理的源码,看看源码先代码审计一下:

const cookieParser = require('cookie-parser');
const express = require('express');
const crypto = require('crypto');
const jwt = require('jsonwebtoken'); const flag = "[redacted]"; let secrets = []; const app = express()
app.use('/style.css', express.static('style.css'));
app.use('/favicon.ico', express.static('favicon.ico'));
app.use('/rick.png', express.static('rick.png'));
app.use(cookieParser()) app.use('/admin',(req, res, next)=>{
res.locals.rolled = true;
next();
}) app.use((req, res, next) => {
let cookie = req.cookies?req.cookies.session:"";
res.locals.flag = false;
try {
let sid = JSON.parse(Buffer.from(cookie.split(".")[1], 'base64').toString()).secretid;
if(sid==undefined||sid>=secrets.length||sid<0){throw "invalid sid"}
let decoded = jwt.verify(cookie, secrets[sid]);
if(decoded.perms=="admin"){
res.locals.flag = true;
}
if(decoded.rolled=="yes"){
res.locals.rolled = true;
}
if(res.locals.rolled) {
req.cookies.session = ""; // generate new cookie
}
} catch (err) {
req.cookies.session = "";
}
if(!req.cookies.session){
let secret = crypto.randomBytes(32)
cookie = jwt.sign({perms:"user",secretid:secrets.length,rolled:res.locals.rolled?"yes":"no"}, secret, {algorithm: "HS256"});
secrets.push(secret);
res.cookie('session',cookie,{maxAge:1000*60*10, httpOnly: true})
req.cookies.session=cookie
res.locals.flag = false;
}
next()
}) app.get('/admin', (req, res) => {
res.send("<!DOCTYPE html><head></head><body><script>setTimeout(function(){location.href='//goo.gl/zPOD'},10)</script></body>");
}) app.get('/', (req, res) => {
res.send("<!DOCTYPE html><head><link href='style.css' rel='stylesheet' type='text/css'></head><body><h1>hello kind user!</h1><p>your flag is <span style='color:red'>"+(res.locals.flag?flag:"error: insufficient permissions! talk to the <a href='/admin'"+(res.locals.rolled?" class='rolled'":"")+">admin</a> if you want access to the flag")+"</span>.</p><footer><small>This site was made extra secure with signed cookies, with a different randomized secret for every cookie!</small></footer></body>")
}) app.listen(3000)

粗略看了一下发现这是一道jwt伪造的题。先来简单讲一下jwt是个什么:

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
 
JWT是由三段信息构成的,将这三段信息文本用符号"."链接一起就构成了Jwt字符串,就像这样:

第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature).

头部(header)承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常使用HS256与RS25

完整的头部就像下面这样的JSON:

在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。然后对头部进行base64编码即可得到我们的第一段jwt。

载荷(Payload)就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

完整的载荷就像下面这样的JSON:

然后对载荷进行Base64加密即可得到我们的第二段jwt,与第一段jwt使用“.”连接。

签证信息(signature)构成jwt的第三部分,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

了解过jwt的原理后,我们来总结一下jwt中容易出现的安全问题,其实也就是CTF关于jwt题目中的考法:

1修改算法为none

  修改算法有两种修改的方式其中一种就是将算法就该为none。

  后端若是支持none算法,header中的alg字段可被修改为none。

  去掉JWT中的signature数据(仅剩header + ‘.’ + payload + ‘.’) 然后直接提交到服务端去。

2修改算法RS256为HS256

  RS256是非对称加密算法,HS是对称加密算法。

  假设jwt内部的函数支持的RS256算法,又同时支持HS256算法

  如果已知公钥的话,将算法改成HS256,然后后端就会用这个公钥当作密钥来加密

3信息泄露、密钥泄露

  JWT是以base64编码传输的,虽然密钥不可见,但是其数据记本上是明文传输的,如果传输了重要的内容,可以base64解码然后获取其重要的信息。

  如果服务端泄露了密钥,用户便可以根据密钥和加密算法来自己伪造生成jwt。

4爆破密钥

  如果密钥比较短,并且已知加密算法,通过暴力破解的方式,可以得到其密钥。

仔细审计一下代码,需要关注的点在这里:

let secret = crypto.randomBytes(32)
cookie = jwt.sign({perms:"user",secretid:secrets.length,rolled:res.locals.rolled?"yes":"no"}, secret, {algorithm: "HS256"});
secrets.push(secret);

再看看获取Flag的条件:

let sid = JSON.parse(Buffer.from(cookie.split(".")[1], 'base64').toString()).secretid;
if(sid==undefined||sid>=secrets.length||sid<0){throw "invalid sid"}
let decoded = jwt.verify(cookie, secrets[sid]);
if(decoded.perms=="admin"){
res.locals.flag = true;
}
if(decoded.rolled=="yes"){
res.locals.rolled = true;
}
if(res.locals.rolled) {
req.cookies.session = ""; // generate new cookie
}

这里我们分析一下:构造Payload首先需要绕过的是对于sid的验证,其次是jwt.verify()的验证,才可以获得Flag

先来看对于sid的验证:

let sid = JSON.parse(Buffer.from(cookie.split(".")[1], 'base64').toString()).secretid; //这里是获取jwt payload中secretid的值
if(sid==undefined||sid>=secrets.length||sid<0){throw "invalid sid"}

要求sid不能为空、值不为负并且要小于或等于secrets的长度值。这里有一个trick: NodeJs中数字与非纯数字字符串比较,无论大小都会返回Flase。

因此绕过这个验证只要保证sid的值即jwt payload中secretid有值即可,无论字符或数字都可以绕过。

接下来看看如何绕过jwt.verify()的验证:

let decoded = jwt.verify(cookie, secrets[sid]);

从通过jwt.verify()验证所需的参数来看,我们需要密钥才可以通过验证,但是我们并不知道密钥,同时密钥是32位随机生成的字符串,爆破显然也行不通

这时我们想到了将jwt Header中加密算法alg的值设置为none即可,并且此时jwt.verify()验证中的密钥secrets[sid]为空即可通过验证(因为没有加密算法所以不需要私钥)

若sid为一个非数字字符串,那么secrets[sid]便会返回undefined,同时程序也只是验证了sid不等于undefined,对secrets[sid]并没有限制。

因此我们构造payload的核心就出来了:

1.将Header中alg的值修改为none

2.将Payload中perms的值修改为admin

3.将Payload中secretid的值修改为任一非数字字符串

4.将Payload中rolled的值修改为no,防止后端重新分配给我们一个jwt

构造Payload步骤如下:

通过这个网站我们可以实现jwt的解码与生成:https://jwt.io/

首先获取我们访问时默认的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwZXJtcyI6InVzZXIiLCJzZWNyZXRpZCI6OTAsInJvbGxlZCI6Im5vIiwiaWF0IjoxNTgzMjk0MTk3fQ.M2OrRjGys_6btzgDXAipdjv4iB5vGovgnWFGQOwRgyo

解密得到:

{
alg: "HS256",
typ: "JWT"
}.
{
perms: "user",
secretid: 90,
rolled: "yes",
iat: 1583294197 //这里是jwt生成的时间,发送的时候有无均可
}.
[signature] //签名部分,这里我们没有密钥,因此接下来伪造jwt的时候需要删掉这一部分

我们来构造一下获取Flag的明文jwt:

{
alg: "none", //不使用加密方式
typ: "JWT"
}.
{
perms: "admin",
secretid: "ye", //这里为了绕过后端的验证需要输入一个字符串
rolled: "no" //设置为no让后端不再重新分配jwt
}.

然后我们生成jwt然后直接删去第三部分的签证得到Payload:

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJwZXJtcyI6ImFkbWluIiwic2VjcmV0aWQiOiJ5ZSIsInJvbGxlZCI6Im5vIn0.

将我们的payload jwt设置为session再次发送请求得到Flag:

参考链接:

https://www.jianshu.com/p/576dbf44b2ae

https://www.freebuf.com/column/207216.html

[AngstromCTF 2019]Cookie Cutter的更多相关文章

  1. android 实现垂直的ProgressBar

    I had recently come across the need for a vertical progress bar but was unable to find a solution us ...

  2. javascript启示录英文单词生词

    odd:奇怪的 represent:代表 primitive:原始的 trivial:平凡的 demonstrate:证明 keep this at the forefront of your min ...

  3. magnum devstack部署

    magnum安装 安装条件: 至少要10G以上内存的机器.亲测使用6G的虚拟机,所有操作均有至少一秒延迟. 硬盘至少50G 良好的上网环境 操作步骤参见快速入门 以下是我操作的步骤记录 sudo mk ...

  4. SEO优化上首页之搜索引擎作弊案例与反作弊原理

    搜索引擎流量价值巨大,有不少人专门研究排名机制,利用搜索引擎漏洞作弊,寻求快速提高网站排名,进而获取更多的流量和利益,甚至有的网站优化公司专门提供作弊服务.搜索引擎为了杜绝这种情况,必须能过滤大量垃圾 ...

  5. 3DSMAX中英文对比大全(从A-Z分类)

    A Absolute Mode Transform Type-in绝对坐标方式变换输入 Absolute/Relative Snap Toggle Mode绝对/相对捕捉开关模式 ACIS Optio ...

  6. JavaScript Constructors

    Understanding JavaScript Constructors It was: 1) This article is technically sound. JavaScript doesn ...

  7. 4 Visual Effects 视觉效果 读书笔记 第四章

    4   Visual Effects    视觉效果        读书笔记 第四章 Well, circles and ovals are good, but how about drawing r ...

  8. 20140401 cudaHOG代码

    1.cudaHOG代码(删减没有必要的目录) cudaHOGDetect需要boost库:boost_date_time-vc100-mt-1_40.lib VC++目录->附加库目录D:\bo ...

  9. [BJDCTF2020]Cookie is so stable && [GWCTF 2019]枯燥的抽奖

    [BJDCTF2020]Cookie is so stable 进入环境后看到有hint,点击之后查看源代码 提示我们cookie有线索 flag页面是: 需要输入一个username,或许这道题目是 ...

随机推荐

  1. CSS品控与流程

    精通CSS意味着不仅能写出可用的标记和样式,还能让代码好阅读.方便移植.易维护. 1.外部代码质量:调试CSS 外部代理质量就是用户能体验到的最终结果.主要体现在几个方面. 正确性.CSS属性名都写对 ...

  2. OS实现流程草稿

    实现一个OS需要现在网上搜索 不要在Windows环境下写 nasm等汇编编译器的语法 bois中断函数使用 内存分布 描述符 实模式与保护模式 从实模式到保护模式比较难,可以从网上找一些代码能运行自 ...

  3. Django 环境下常用的模型设计

    Django 环境下常用的模型设计 用户表 继承 django.contrib.auth.model import AbstractUser AbstractUser 默认已经包含了很多字段了 id ...

  4. 手写IOC实现过程

    一.手写ioc前基础知识 1.什么是IOC(Inversion of Control 控制反转)? IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合.更优良 ...

  5. Jenkins持续集成(下)-Jenkins部署Asp.Net网站自动发布

    环境:Windows 2008 R2.Jenkins2.235.1.Visual Studio 2017: 概要 前面写过一篇文章,<自动发布-asp.net自动发布.IIS站点自动发布(集成S ...

  6. java流程控制语句if

    一 if语句 if语句是指如果满足某种条件,就进行某种处理. 格式: if (条件语句){ 执行语句; …… } 流程图: 例如: public class IfDemo01 { public sta ...

  7. 使用Python语言通过PyQt5和socket实现UDP服务器

    前言 最近做了一个小软件,记录一下相关内容. 已有条件 现在已有一个硬件设备作为客户端(暂称其为"电路"). 基于SIM卡,电路可以通过UDP协议传输数据(程序已经内置在电路中), ...

  8. externaltrafficpolicy的有关问题说明

    环境描述 生产环境通过gitlab-running实现自动化发布业务,现需要收集客户端的真实ip,需要将externaltrafficpolicy改为lacal模式(原来是cluster模式),前天开 ...

  9. python header设置随机user_agent

    1 安装 fake_useragent pip install fake_useragent 2 使用 # -*- coding:utf-8 -*- from fake_useragent impor ...

  10. RFC2474 - Definition of the Differentiated Services Field (DS Field) in the IPv4 and IPv6 Headers的双语版

    RFC2474 - Definition of the Differentiated Services Field (DS Field) in the IPv4 and IPv6 Headers英文版 ...