别着急,坐和放宽
使用社交账号登录
访问控制(Access Control)也称为授权(Authorization),是确保用户只能访问其被授权资源的安全机制。
形象比喻: 想象你住在一个公寓楼里:
如果访问控制失效,就相当于任何住户都能用门禁卡打开整栋楼的所有房间,包括他人家门和管理员办公室,这将带来严重的安全隐患。
访问控制漏洞主要分为两种类型,理解它们的关键是区分权限的"水平"与"垂直"维度。
定义: 相同权限级别的用户能够访问彼此的私有数据。
比喻: 你和邻居都是普通住户,但你却能用自己的钥匙打开邻居的家门。
实际案例:
正常访问: https://example.com/user/profile?userId=123 (查看自己的资料)
攻击尝试: https://example.com/user/profile?userId=124 (查看他人的资料)
如果服务器未验证当前登录用户是否是资源的合法所有者,攻击者只需修改URL参数就能访问其他用户的敏感信息(电话、地址、订单等)。
定义: 低权限用户能够执行高权限用户才能进行的操作。
比喻: 普通住户不仅能打开自己家门,还能进入物业办公室操作整栋楼的监控系统。
实际案例:
/api/admin/deleteUser 未进行权限校验危害性: 垂直越权通常比水平越权危害更大,可能导致系统被完全控制。
直接修改URL参数或请求体中的关键字段:
/order/detail?orderId=1001 → orderId=1002
/api/user/update → {"userId": 999, "role": "admin"}
尝试访问未授权的URL路径:
/admin
/config
/api/internal/settings
/../../../etc/passwd (路径遍历攻击)
篡改身份凭证或会话信息:
永远不要信任客户端传来的任何权限信息,所有授权决策必须在服务器端执行。
确保每个用户或服务只拥有完成其任务所需的最小权限集。
Gin框架实现示例:
每个资源访问都必须验证:
防止水平越权示例:
基于角色的访问控制 (RBAC):
避免在URL中直接暴露数据库ID,使用UUID或加密ID:
实现访问日志和异常检测:
失效的访问控制是OWASP Top 10中排名第一的安全风险,其危害巨大且极为普遍。这类漏洞本质上是逻辑缺陷,源于开发过程中对权限校验的疏忽。
防护要点:
package middleware
import (
"net/http"
"github.com/gin-gonic/gin"
)
// RequireRole checks if user has required role
func RequireRole(requiredRole string) gin.HandlerFunc {
return func(c *gin.Context) {
// Get user role from JWT token or session
userRole, exists := c.Get("userRole")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
if userRole.(string) != requiredRole {
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
c.Abort()
return
}
c.Next()
}
}
// Usage in routes
func SetupRoutes(r *gin.Engine) {
// Public routes
r.GET("/api/products", GetProducts)
// User routes (requires authentication)
userGroup := r.Group("/api/user")
userGroup.Use(AuthRequired())
{
userGroup.GET("/profile", GetUserProfile)
userGroup.PUT("/profile", UpdateUserProfile)
}
// Admin routes (requires admin role)
adminGroup := r.Group("/api/admin")
adminGroup.Use(AuthRequired(), RequireRole("admin"))
{
adminGroup.DELETE("/users/:id", DeleteUser)
adminGroup.GET("/stats", GetSystemStats)
}
}
// GetOrderDetail ensures user can only access their own orders
func GetOrderDetail(c *gin.Context) {
orderID := c.Param("id")
// Get current user ID from token
currentUserID, _ := c.Get("userID")
// Query order from database
var order Order
if err := db.Where("id = ?", orderID).First(&order).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
return
}
// Critical: Verify ownership before returning data
if order.UserID != currentUserID.(uint) {
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
return
}
c.JSON(http.StatusOK, order)
}
package rbac
import "github.com/gin-gonic/gin"
type Permission string
const (
PermReadUser Permission = "user:read"
PermWriteUser Permission = "user:write"
PermDeleteUser Permission = "user:delete"
PermReadAdmin Permission = "admin:read"
)
var RolePermissions = map[string][]Permission{
"user": {
PermReadUser,
PermWriteUser,
},
"admin": {
PermReadUser,
PermWriteUser,
PermDeleteUser,
PermReadAdmin,
},
}
func RequirePermission(required Permission) gin.HandlerFunc {
return func(c *gin.Context) {
userRole, _ := c.Get("userRole")
permissions := RolePermissions[userRole.(string)]
hasPermission := false
for _, perm := range permissions {
if perm == required {
hasPermission = true
break
}
}
if !hasPermission {
c.JSON(403, gin.H{"error": "Permission denied"})
c.Abort()
return
}
c.Next()
}
}
import "github.com/google/uuid"
// Create order with UUID
func CreateOrder(c *gin.Context) {
order := Order{
UUID: uuid.New().String(), // Use UUID instead of auto-increment ID
UserID: getCurrentUserID(c),
// ... other fields
}
db.Create(&order)
c.JSON(200, gin.H{"orderUUID": order.UUID})
}
// Access order by UUID
func GetOrder(c *gin.Context) {
orderUUID := c.Param("uuid")
var order Order
if err := db.Where("uuid = ? AND user_id = ?",
orderUUID, getCurrentUserID(c)).First(&order).Error; err != nil {
c.JSON(404, gin.H{"error": "Order not found"})
return
}
c.JSON(200, order)
}
func AccessLogger() gin.HandlerFunc {
return func(c *gin.Context) {
userID, _ := c.Get("userID")
// Log access attempt
log.Printf("Access: User=%v, Method=%s, Path=%s, IP=%s",
userID, c.Request.Method, c.Request.URL.Path, c.ClientIP())
c.Next()
// Log failed authorization attempts
if c.Writer.Status() == http.StatusForbidden {
log.Printf("SECURITY: Unauthorized access attempt - User=%v, Path=%s",
userID, c.Request.URL.Path)
// Trigger alert if too many failures
checkAndAlertAnomalousActivity(userID, c.Request.URL.Path)
}
}
}