Skip to content

Commit

Permalink
feat(web/python-prototype-pollution): 添加 README、源码、工作流文件 (#2)
Browse files Browse the repository at this point in the history
* feat(web/python-prototype-pollution): 添加 README

* feat(web/python-prototype-pollution): 添加源码及构建文件

* ci(web/python-prototype-pollution): 添加工作流文件
  • Loading branch information
13m0n4de authored Sep 14, 2024
1 parent 9f86bf9 commit 01eabbc
Show file tree
Hide file tree
Showing 9 changed files with 694 additions and 0 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/web.python_prototype_pollution.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Challenge 拼尽全力也无法越权

on:
push:
branches: ["main"]
paths:
- "!**/README.md"
- "challenges/web/python_prototype_pollution/build/**"
workflow_dispatch:

env:
TYPE: web
NAME: python_prototype_pollution
REGISTRY: ghcr.io

jobs:
challenge-build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ github.repository }}/${{ env.NAME }}
tags: |
latest
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: challenges/${{ env.TYPE }}/${{ env.NAME }}/build
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
push: true
16 changes: 16 additions & 0 deletions challenges/web/python_prototype_pollution/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# 拼尽全力也无法越权

- 作者:13m0n4de
- 参考:-
- 难度:-
- 分类:Web
- 镜像:-
- 端口:-

## 题目描述

<description>

## 题目解析

<analysis>
15 changes: 15 additions & 0 deletions challenges/web/python_prototype_pollution/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM python:3.12-alpine

WORKDIR /app

COPY app/requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

COPY app/main.py /app/main.py
COPY app/static/index.html /app/static/index.html
COPY app/static/index-compiled.js /app/static/index-compiled.js

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
121 changes: 121 additions & 0 deletions challenges/web/python_prototype_pollution/build/app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import os
from typing import Dict, List, Optional

import jwt
from pydantic import BaseModel
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse


app = FastAPI(openapi_url=None)
SECRET_KEY: str = os.urandom(32).hex()
FLAG = os.environ.get("GZCTF_FLAG", "SVUCTF{test_flag}")


class User(BaseModel):
username: str
password: str

def get_info(self):
return f"User: {self.username}"


class LoginRequest(BaseModel):
username: str
password: str


users_db: List[User] = [
User(username="admin", password=os.urandom(32).hex()),
User(username="user", password="123456"),
]

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


def merge(src, dst) -> None:
for k, v in src.items():
if isinstance(dst, dict):
if dst.get(k) and isinstance(v, dict):
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and isinstance(v, dict):
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)


def get_user(username: str) -> Optional[User]:
return next((user for user in users_db if user.username == username), None)


def decode_token(token: str) -> dict:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return payload
except jwt.PyJWTError as e:
raise HTTPException(status_code=401, detail="Invalid token") from e


app.mount("/static", StaticFiles(directory="static"), name="static")


@app.get("/")
async def read_index():
return FileResponse("static/index.html")


@app.post("/api/login")
async def login(login_data: LoginRequest):
user = get_user(login_data.username)
if not user or user.password != login_data.password:
raise HTTPException(status_code=401, detail="Incorrect username or password")
token = jwt.encode(
{"sub": user.username, "role": "user"}, SECRET_KEY, algorithm="HS256"
)
return {"access_token": token, "token_type": "bearer"}


@app.post("/api/update_user")
async def update_user(user_update: Dict, token: str = Depends(oauth2_scheme)):
payload = decode_token(token)

username = payload.get("sub")
if not username:
raise HTTPException(status_code=404, detail="User not found")

user = get_user(username)
if not user:
raise HTTPException(status_code=404, detail="User not found")

merge(user_update, user)

new_token = jwt.encode(
{"sub": user.username, "role": payload.get("role", "user")},
SECRET_KEY,
algorithm="HS256",
)

return {"message": "User updated successfully", "new_token": new_token}


@app.get("/api/get_flag")
async def get_flag(token: str = Depends(oauth2_scheme)):
payload = decode_token(token)
role = payload.get("role")

if not role or role != "admin":
raise HTTPException(status_code=403, detail="Admin access required")

return {"flag": FLAG}


# @app.get("/debug")
# async def debug():
# return {
# "secret_key": SECRET_KEY,
# "users_db": [user.model_dump() for user in users_db],
# }
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fastapi==0.114.2
pydantic==2.9.1
PyJWT==2.9.0
uvicorn==0.30.6
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
package.json
yarn.lock
.babelrc
Loading

0 comments on commit 01eabbc

Please sign in to comment.