第一次做生产环境的项目和运维,也是出现了很多问题,下面是遇到的其中一个问题。

绕过WAF向OJ提交代码的方案

随着网络安全越来越严格,防火墙也是做了很多升级,有些防火墙为了防止上传恶意代码甚至暴力匹配某些字符或者是字符组合,这对于ACM选手来说是不太友好的,因为特殊的需要,我们经常要向服务器提交我们写的代码,里面难免会出现一些所谓的“恶意字符”,因此需要对OJ进行特殊地处理来使得代码不被WAF拦截。

一切从实际出发

OJ平台:HUSTOJ

先来看看选手向我们报告的有问题的代码:

1
2
3
4
5
6
#include<stdio.h>
int main(){
int a;
scanf("%d",&a);
printf("%d %d",a/7*2,a/7*2+a%7);
}

看起来只是一个很简单的输入输出功能,对于ACMer来说这段代码再正常不过了,但是提交给OJ直接显示网络错误。

BP抓包,分析这一段HTTP请求:

返回是一段空的包,浏览器收到的就是 empty response,wireshark再抓包可以发现

服务端直接给了一个 rst 报文,rst 报文一般是出现了严重错误才会进行的一个重置操作,当然此时还不能下论断就是因为 防火墙的拦截导致而不是其它的错误。那么可以登录服务器查询服务器日志判断服务器是否收到报文,如果没收到就说明报文段应该是在到达服务器之前被 drop 了。

HUSTOJ 使用的是 Nginx 服务器,通过命令 tail -f /var/log/nginx/access.log 来查看访问日志。

再次进行提交发现:

经过数次的提交并没有对 submit.php 进行 POST 请求的操作,说明 Nginx 根本不知道有这个请求,那么就可以 100% 断定该包被防火墙拦截了。

当然,该OJ内置了解决方案,在审计OJ代码的时候发现了它提供了一个 encode 提交。

所以我选择把 $OJ_ENCODE_SUBMIT 给打开了,我也去查看了它的方案,即对提交的代码进行了简单的base64编码,但是依然会被拦截。

同样抓包下来,发现依然对 base64 编码的结果有所拦截,并且服务器同样没有日志记录,说明是没有收到请求的。

防火墙会对简单的base64加密做拦截,因此我们采取换表的策略绕过,换表要同时对前端和后端的源码进行修改,HUSTOJ上,在 /submit.php 当中,包含一个自己写的php文件,然后用自己换好的表,再把base64编码写一遍,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
//base64.php
function decode64($input) {
$keyStr = "3WiPZ+xr/yKf5OdQ6UATHYItebL8B0njk192cJRNagGm7hoECvFVpqw4DsMlzuXS=";
$output = "";
$chr1="";
$chr2="";
$chr3="";
$enc1=$enc2=$enc3=$enc4="";
$i = 0;
if (strlen($input) % 4 != 0) {
return "";
}
$len=strlen($input);
do {
$enc1 = strpos($keyStr,$input[$i++]);
$enc2 = strpos($keyStr,$input[$i++]);
$enc3 = strpos($keyStr,$input[$i++]);
$enc4 = strpos($keyStr,$input[$i++]);
$chr1 = ($enc1 << 2) | ($enc2 >> 4);
$chr2 = (($enc2 & 15) << 4) | ($enc3 >> 2);
$chr3 = (($enc3 & 3) << 6) | $enc4;
$output = $output.chr($chr1);
if ($enc3 != 64) {
$output .= chr($chr2);
}
if ($enc4 != 64) {
$output .= chr($chr3);
}
$chr1 = $chr2 = $chr3 = "";
$enc1 = $enc2 = $enc3 = $enc4 = "";
} while ($i < $len);
return $output;
}
?>

再将 /include/base64.js 中的前端代码中 keyStr 变量替换一下,使用一个换表的base64编码就不会被拦截了。

可以发现成功提交了,这样我们就完成了对 WAF 的绕过。

锋芒毕露

根据这个,我也是希望其它同样被 WAF 拦截的 OJ 也能也能参考本方案去解决问题,因此我向本开源项目的作者提出了 PR,也是非常感谢作者的信任,在 2 个小时之后 pr 被 merge 了。