CSRF protection implementation solutions
Basic Principles of CSRF Attacks
CSRF (Cross-Site Request Forgery) is a common web security threat. Attackers trick users into performing unintended actions on an authenticated web application. For example, after a user logs into a banking website, visiting a malicious site might automatically submit a transfer request to the banking site, exploiting the user's logged-in state to complete unauthorized operations.
Three conditions are required for a successful attack:
- The user is logged into the target website.
- The user visits a malicious site without logging out.
- The target website's interface lacks sufficient protective measures.
// Code that might be included on a malicious site
<img src="http://bank.com/transfer?to=hacker&amount=10000" width="0" height="0">
CSRF Protection Mechanisms in Koa2
The Koa2 ecosystem offers multiple CSRF protection solutions, primarily implemented through the following methods:
- Synchronizer Token Pattern: The server generates a token embedded in forms and validates it upon submission.
- Double Cookie Verification: The client stores a token and includes it in requests.
- SameSite Cookie Attribute: Restricts the use of third-party cookies.
The most commonly used solution is the koa-csrf
middleware, which implements the synchronizer token pattern:
const CSRF = require('koa-csrf')
const session = require('koa-session')
app.keys = ['some secret key']
app.use(session(app))
app.use(new CSRF())
Implementing Synchronizer Token Protection
The Synchronizer Token Pattern is a classic CSRF protection solution. The server generates a unique token stored in the session and sends it to the client. The client must include this token when submitting forms, and the server verifies if the token matches.
Steps to implement in Koa2:
- Install dependencies:
npm install koa-session koa-csrf
- Configure middleware:
const Koa = require('koa')
const session = require('koa-session')
const CSRF = require('koa-csrf')
const app = new Koa()
app.keys = ['your-session-secret']
app.use(session(app))
// Configure CSRF
app.use(new CSRF({
invalidTokenMessage: 'Invalid CSRF token',
invalidTokenStatusCode: 403,
excludedMethods: ['GET', 'HEAD', 'OPTIONS'],
disableQuery: false
}))
- Retrieve the token in routes:
router.get('/form', async (ctx) => {
await ctx.render('form', {
csrf: ctx.csrf
})
})
- Include the token in forms:
<form action="/submit" method="POST">
<input type="hidden" name="_csrf" value="<%= csrf %>">
<!-- Other form fields -->
<button type="submit">Submit</button>
</form>
Double Cookie Verification Solution
For API interfaces or frontend-backend separation scenarios, double cookie verification can be used:
- Set an HttpOnly authentication cookie upon successful login.
- Simultaneously set a non-HttpOnly CSRF token cookie.
- The frontend reads the CSRF token from the cookie and adds it to the request headers.
- The server compares the token in the request headers with the one in the cookie.
Implementation code:
// Set CSRF Token Cookie
app.use(async (ctx, next) => {
if (ctx.path === '/login' && ctx.method === 'POST') {
const token = generateToken()
ctx.cookies.set('csrf-token', token, {
httpOnly: false,
sameSite: 'strict'
})
}
await next()
})
// Verification middleware
app.use(async (ctx, next) => {
if (['POST', 'PUT', 'DELETE'].includes(ctx.method)) {
const cookieToken = ctx.cookies.get('csrf-token')
const headerToken = ctx.get('x-csrf-token')
if (!cookieToken || cookieToken !== headerToken) {
ctx.throw(403, 'Invalid CSRF Token')
}
}
await next()
})
Frontend implementation:
// Get CSRF Token
function getCSRFToken() {
return document.cookie.replace(/(?:(?:^|.*;\s*)csrf-token\s*\=\s*([^;]*).*$)|^.*$/, "$1")
}
// Add header when sending requests
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCSRFToken()
},
body: JSON.stringify({...})
})
SameSite Cookie Attribute
Modern browsers support the SameSite cookie attribute, which effectively prevents CSRF attacks:
Strict
: Completely prohibits third-party cookies.Lax
: Loose mode, allows some secure requests to carry cookies.None
: Disables SameSite protection.
Configuration in Koa2:
ctx.cookies.set('sessionId', '12345', {
sameSite: 'strict',
httpOnly: true,
secure: true
})
Custom CSRF Protection Middleware
For more flexible solutions, custom middleware can be created:
const crypto = require('crypto')
app.use(async (ctx, next) => {
// Generate token
ctx.generateCSRFToken = () => {
const token = crypto.randomBytes(32).toString('hex')
ctx.session.csrfToken = token
return token
}
// Verify token
ctx.verifyCSRFToken = (token) => {
return ctx.session.csrfToken &&
ctx.session.csrfToken === token
}
await next()
})
// Usage example
router.post('/submit', async (ctx) => {
const { _csrf } = ctx.request.body
if (!ctx.verifyCSRFToken(_csrf)) {
ctx.throw(403, 'Invalid CSRF token')
}
// Process legitimate requests
})
CSRF Protection for AJAX Requests
Single-page applications (SPAs) require special handling:
- The server embeds the CSRF token in the initial HTML:
<meta name="csrf-token" content="<%= csrfToken %>">
- The frontend globally sets AJAX request headers:
const csrfToken = document.querySelector('meta[name="csrf-token"]').content
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken
- Server verification middleware:
app.use(async (ctx, next) => {
if (ctx.is('application/json')) {
const token = ctx.get('x-csrf-token')
if (!token || token !== ctx.session.csrfToken) {
ctx.throw(403, 'Invalid CSRF token')
}
}
await next()
})
Security Enhancement Measures
Beyond basic protection, the following measures can be taken:
- Verify Origin/Referer Headers:
app.use(async (ctx, next) => {
const origin = ctx.get('origin')
const referer = ctx.get('referer')
if (['POST', 'PUT', 'DELETE'].includes(ctx.method)) {
if (origin && !origin.match(/^https?:\/\/(www\.)?yourdomain\.com$/)) {
ctx.throw(403, 'Request origin not allowed')
}
}
await next()
})
- Require Re-authentication for Critical Operations:
router.post('/transfer', async (ctx) => {
const { amount, password } = ctx.request.body
if (!verifyPassword(ctx.state.user.id, password)) {
ctx.throw(403, 'Password verification failed')
}
// Execute transfer operation
})
- Limit Sensitive Operation Frequency:
const rateLimit = require('koa-ratelimit')
const transferLimiter = rateLimit({
driver: 'memory',
db: new Map(),
duration: 60000,
max: 3,
id: (ctx) => ctx.state.user.id
})
router.post('/transfer', transferLimiter, async (ctx) => {
// Transfer logic
})
Testing CSRF Protection
Ensure protective measures are effective by testing:
- Use Postman to simulate cross-origin requests.
- Attempt to send requests without a CSRF token.
- Test if token reuse is rejected.
- Verify the SameSite attribute of cookies.
Example test script:
const request = require('supertest')
const app = require('../app')
describe('CSRF Protection', () => {
it('should reject request without CSRF token', async () => {
const res = await request(app)
.post('/submit')
.send({ data: 'test' })
expect(res.status).toEqual(403)
})
it('should accept request with valid CSRF token', async () => {
const agent = request.agent(app)
const getRes = await agent.get('/form')
const csrfToken = extractTokenFromResponse(getRes)
const postRes = await agent
.post('/submit')
.send({ _csrf: csrfToken, data: 'test' })
expect(postRes.status).toEqual(200)
})
})
Performance Optimization Considerations
CSRF protection may impact performance. Optimization suggestions:
- Exclude routes that don't need protection:
app.use(new CSRF({
excludedPaths: [/^\/api\/public/, /^\/healthcheck/]
}))
- Cache CSRF Tokens:
const tokenCache = new LRU({ max: 1000 })
app.use(async (ctx, next) => {
if (ctx.method === 'GET' && ctx.path === '/csrf-token') {
const cachedToken = tokenCache.get(ctx.session.id)
if (cachedToken) {
ctx.body = { token: cachedToken }
return
}
// Generate new token
}
await next()
})
- Use Faster Token Generation Algorithms:
function generateToken() {
return crypto.randomBytes(16).toString('hex') // Faster than 32 bytes
}
Integration with Other Security Measures
CSRF protection should work in conjunction with other security measures:
- CSP Policy:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
- XSS Protection:
app.use(require('koa-helmet')())
- Enforce HTTPS:
app.use(async (ctx, next) => {
if (ctx.protocol !== 'https' && process.env.NODE_ENV === 'production') {
ctx.redirect(`https://${ctx.host}${ctx.url}`)
return
}
await next()
})
Common Issues and Solutions
Issue 1: AJAX requests cannot retrieve CSRF tokens.
Solution: Ensure tokens are exposed to the frontend via meta tags or initial response bodies.
Issue 2: Multiple tabs cause token invalidation.
Solution: Generate new tokens on each page load while allowing old tokens to remain valid for a short period.
app.use(new CSRF({
tokenLifecycle: 3600000 // 1-hour validity
}))
Issue 3: Mobile API clients cannot handle CSRF.
Solution: Disable CSRF for specific routes and use alternative authentication methods like OAuth.
app.use(new CSRF({
excludedPaths: [/^\/mobile-api/]
}))
Logging and Monitoring
Logging CSRF verification failures helps detect attack attempts:
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
if (err.status === 403 && err.message.includes('CSRF')) {
logCSRFAttempt(ctx.ip, ctx.path)
}
throw err
}
})
function logCSRFAttempt(ip, path) {
console.warn(`CSRF attempt from ${ip} at ${new Date().toISOString()} on ${path}`)
// Integrate with security monitoring systems
}
Best Practices for Framework Integration
Integration with common Koa2 frameworks:
- Integration with Koa-Router:
router.get('/protected', csrfMiddleware, async (ctx) => {
ctx.body = { token: ctx.csrf }
})
router.post('/protected', csrfMiddleware, async (ctx) => {
// Automatically verify CSRF
})
- Integration with Passport:
app.use(passport.initialize())
app.use(passport.session())
app.use(new CSRF()) // CSRF after authentication
- Integration with GraphQL:
const { ApolloServer } = require('apollo-server-koa')
const server = new ApolloServer({
context: ({ ctx }) => {
return {
csrfToken: ctx.csrf
}
}
})
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:常见 Web 安全威胁分析
下一篇:XSS 攻击防御措施