API07: Server Side Request Forgery - Code Examples
Flask (Python)
Vulnerable
``python
@app.route('/api/fetch', methods=['POST'])
def fetch():
url = request.json['url']
return jsonify(requests.get(url).json()) # VULNERABLE!
`
Secure
`python
import ipaddress
import socket
ALLOWED_DOMAINS = ['api.example.com']
def validate_url(url):
parsed = urllib.parse.urlparse(url)
if parsed.scheme not in ['http', 'https']:
return False
hostname = parsed.hostname
if not any(hostname.endswith(d) for d in ALLOWED_DOMAINS):
return False
ip = ipaddress.ip_address(socket.gethostbyname(hostname))
if ip.is_private or ip.is_loopback:
return False
return True
@app.route('/api/fetch', methods=['POST'])
def fetch_secure():
url = request.json['url']
if not validate_url(url):
return jsonify({'error': 'Invalid URL'}), 400
return jsonify(requests.get(url, timeout=5, allow_redirects=False).json())
`
Express (Node.js)
Vulnerable
`javascript
app.post('/api/fetch', async (req, res) => {
const data = await axios.get(req.body.url);
res.json(data.data);
});
`
Secure
`javascript
const ALLOWED_DOMAINS = ['api.example.com'];
async function validateUrl(url) {
const parsed = new URL(url);
if (!ALLOWED_DOMAINS.some(d => parsed.hostname.endsWith(d))) {
throw new Error('Domain not allowed');
}
const addr = ipaddr.parse((await dns.resolve4(parsed.hostname))[0]);
if (addr.range() !== 'unicast') {
throw new Error('Private IP');
}
}
app.post('/api/fetch', async (req, res) => {
try {
await validateUrl(req.body.url);
const data = await axios.get(req.body.url, {timeout: 5000, maxRedirects: 0});
res.json(data.data);
} catch(e) {
res.status(400).json({error: e.message});
}
});
`
Spring Boot (Java)
Vulnerable
`java
@PostMapping("/fetch")
public ResponseEntity> fetch(@RequestBody Map body) {
String url = body.get("url");
RestTemplate rest = new RestTemplate();
return ResponseEntity.ok(rest.getForObject(url, String.class));
}
`
Secure
`java
private static final List ALLOWED_DOMAINS = Arrays.asList("api.example.com");
private boolean validateUrl(String urlString) throws Exception {
URL url = new URL(urlString);
if (!ALLOWED_DOMAINS.stream().anyMatch(d -> url.getHost().endsWith(d))) {
return false;
}
InetAddress addr = InetAddress.getByName(url.getHost());
return !addr.isLoopbackAddress() && !addr.isLinkLocalAddress() && !addr.isSiteLocalAddress();
}
@PostMapping("/fetch")
public ResponseEntity> fetchSecure(@RequestBody Map body) {
try {
String url = body.get("url");
if (!validateUrl(url)) {
return ResponseEntity.badRequest().body("Invalid URL");
}
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(5000);
RestTemplate rest = new RestTemplate(factory);
return ResponseEntity.ok(rest.getForObject(url, String.class));
} catch (Exception e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
`
ASP.NET Core (C#)
Vulnerable
`csharp
[HttpPost("fetch")]
public async Task Fetch([FromBody] FetchRequest req) {
using var client = new HttpClient();
var data = await client.GetStringAsync(req.Url);
return Ok(data);
}
`
Secure
`csharp
private static readonly List AllowedDomains = new() { "api.example.com" };
private async Task ValidateUrl(string urlString) {
var uri = new Uri(urlString);
if (!AllowedDomains.Any(d => uri.Host.EndsWith(d))) return false;
var addresses = await Dns.GetHostAddressesAsync(uri.Host);
var addr = addresses[0];
return !IPAddress.IsLoopback(addr) && !addr.IsIPv6LinkLocal;
}
[HttpPost("fetch")]
public async Task FetchSecure([FromBody] FetchRequest req) {
if (!await ValidateUrl(req.Url))
return BadRequest("Invalid URL");
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
var data = await client.GetStringAsync(req.Url);
return Ok(data);
}
``