-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
160 lines (133 loc) · 4.46 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import asyncio
import json
import random
import time
from fastapi import FastAPI, Form
from pydantic import BaseModel
from starlette.middleware.cors import CORSMiddleware
from starlette.requests import Request
from starlette.responses import HTMLResponse, JSONResponse, Response, RedirectResponse
from starlette.templating import Jinja2Templates
app = FastAPI()
templates = Jinja2Templates(directory="templates")
powered_by = [
"RCE as a feature.",
"Unicorns & fairy dust.",
"Three pi's in a trench-coat.",
"An overworked, underpaid hamster.",
"Pretty graphs for someone who dropped a stats minor.",
"You seeing this proves I can make software which doesn't only break.",
]
with open("creds.json", "r") as f:
data: dict[str, str] = json.loads(f.read())
origins = ["http://127.0.0.1:8000", "https://tbue.skelmis.co.nz"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class Message(BaseModel):
message: str
class Success(Message):
class Config:
schema_extra = {
"example": {
"message": "Successful login.",
}
}
class Failure(Message):
class Config:
schema_extra = {
"example": {
"message": "Login failed.",
}
}
@app.middleware("http")
async def timer_injection(request: Request, call_next):
start = time.time()
response: Response = await call_next(request)
finish = time.time()
response.headers["X-TIME-MS"] = str((finish - start) * 1000)
return response
@app.middleware("http")
async def header_injection(request: Request, call_next):
response: Response = await call_next(request)
response.headers["X-POWERED-BY"] = random.choice(powered_by)
return response
@app.get("/")
async def root():
return RedirectResponse(url="/docs")
@app.get("/login/{number}", response_class=HTMLResponse)
async def login_form(request: Request, number: int):
return templates.TemplateResponse(
"login_form.html", {"request": request, "number": number}
)
@app.post(
"/login/1",
responses={
200: {"model": Success},
401: {"model": Failure},
},
description="Your typical login form, enumeration based on status codes or response body.",
)
async def login_one(username: str = Form(), password: str = Form()):
pw = data.get(username, False)
if pw and pw == password:
return JSONResponse(content={"message": "Success"}, status_code=200)
return JSONResponse(content={"message": "Invalid authentication"}, status_code=401)
@app.post(
"/login/2",
responses={
200: {"model": Message},
400: {"model": Message},
},
description="A login form with no (visible) information leakage.",
)
async def login_two(username: str = Form(), password: str = Form()):
# Example real world flows are more like
# if user doesnt exist:
# return failed
#
# # Expensive check here only applies
# # to valid users for the site
# if password is not correct:
# return failed
#
# return logged in
await asyncio.sleep(random.randint(2, 6) / 100)
pw = data.get(username, False)
if pw:
# Mimic an expensive password comparison
await asyncio.sleep(random.randint(4, 7) / 100)
if pw == password:
return JSONResponse(content={"message": "Success"}, status_code=200)
return JSONResponse(content={"message": "Invalid authentication"}, status_code=400)
@app.post(
"/login/3",
responses={
200: {"model": Message},
400: {"model": Message},
},
description="A login form with no information leakage.<br>"
"TBUE is mitigated by constant response times regardless of auth state.",
)
async def login_three(username: str = Form(), password: str = Form()):
# Example real world flows are more like
# if user doesnt exist:
# return failed
#
# # Expensive check here only applies
# # to valid users for the site
# if password is not correct:
# return failed
#
# return logged in
pw = data.get(username, False)
# All requests get roughly the same
# response times, enough to mitigate TBUE
await asyncio.sleep(random.randint(5, 15) / 100)
if pw and pw == password:
return JSONResponse(content={"message": "Success"}, status_code=200)
return JSONResponse(content={"message": "Invalid authentication"}, status_code=400)