From 01eabbc0f40c59479b52d7c922928c3971823aff Mon Sep 17 00:00:00 2001 From: 13m0n4de <136407746+13m0n4de@users.noreply.github.com> Date: Sun, 15 Sep 2024 06:28:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(web/python-prototype-pollution):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20README=E3=80=81=E6=BA=90=E7=A0=81=E3=80=81?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E6=B5=81=E6=96=87=E4=BB=B6=20(#2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(web/python-prototype-pollution): 添加 README * feat(web/python-prototype-pollution): 添加源码及构建文件 * ci(web/python-prototype-pollution): 添加工作流文件 --- .../web.python_prototype_pollution.yml | 50 +++ .../web/python_prototype_pollution/README.md | 16 + .../build/Dockerfile | 15 + .../build/app/main.py | 121 +++++++ .../build/app/requirements.txt | 4 + .../build/app/static/.gitignore | 4 + .../build/app/static/index-compiled.js | 294 ++++++++++++++++++ .../build/app/static/index.html | 18 ++ .../build/app/static/index.js | 172 ++++++++++ 9 files changed, 694 insertions(+) create mode 100644 .github/workflows/web.python_prototype_pollution.yml create mode 100644 challenges/web/python_prototype_pollution/README.md create mode 100644 challenges/web/python_prototype_pollution/build/Dockerfile create mode 100644 challenges/web/python_prototype_pollution/build/app/main.py create mode 100644 challenges/web/python_prototype_pollution/build/app/requirements.txt create mode 100644 challenges/web/python_prototype_pollution/build/app/static/.gitignore create mode 100644 challenges/web/python_prototype_pollution/build/app/static/index-compiled.js create mode 100644 challenges/web/python_prototype_pollution/build/app/static/index.html create mode 100644 challenges/web/python_prototype_pollution/build/app/static/index.js diff --git a/.github/workflows/web.python_prototype_pollution.yml b/.github/workflows/web.python_prototype_pollution.yml new file mode 100644 index 0000000..6097ee8 --- /dev/null +++ b/.github/workflows/web.python_prototype_pollution.yml @@ -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 diff --git a/challenges/web/python_prototype_pollution/README.md b/challenges/web/python_prototype_pollution/README.md new file mode 100644 index 0000000..508b0dd --- /dev/null +++ b/challenges/web/python_prototype_pollution/README.md @@ -0,0 +1,16 @@ +# 拼尽全力也无法越权 + +- 作者:13m0n4de +- 参考:- +- 难度:- +- 分类:Web +- 镜像:- +- 端口:- + +## 题目描述 + + + +## 题目解析 + + diff --git a/challenges/web/python_prototype_pollution/build/Dockerfile b/challenges/web/python_prototype_pollution/build/Dockerfile new file mode 100644 index 0000000..880b475 --- /dev/null +++ b/challenges/web/python_prototype_pollution/build/Dockerfile @@ -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"] diff --git a/challenges/web/python_prototype_pollution/build/app/main.py b/challenges/web/python_prototype_pollution/build/app/main.py new file mode 100644 index 0000000..07cc869 --- /dev/null +++ b/challenges/web/python_prototype_pollution/build/app/main.py @@ -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], +# } diff --git a/challenges/web/python_prototype_pollution/build/app/requirements.txt b/challenges/web/python_prototype_pollution/build/app/requirements.txt new file mode 100644 index 0000000..b0a3160 --- /dev/null +++ b/challenges/web/python_prototype_pollution/build/app/requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.114.2 +pydantic==2.9.1 +PyJWT==2.9.0 +uvicorn==0.30.6 diff --git a/challenges/web/python_prototype_pollution/build/app/static/.gitignore b/challenges/web/python_prototype_pollution/build/app/static/.gitignore new file mode 100644 index 0000000..b6db0b9 --- /dev/null +++ b/challenges/web/python_prototype_pollution/build/app/static/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +package.json +yarn.lock +.babelrc diff --git a/challenges/web/python_prototype_pollution/build/app/static/index-compiled.js b/challenges/web/python_prototype_pollution/build/app/static/index-compiled.js new file mode 100644 index 0000000..3b9d7d5 --- /dev/null +++ b/challenges/web/python_prototype_pollution/build/app/static/index-compiled.js @@ -0,0 +1,294 @@ +"use strict"; + +function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } +function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; } +function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); } +function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; } +function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } +function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } +function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } +function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } +function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } +var API_BASE_URL = '/api'; +var Alert = function Alert(_ref) { + var message = _ref.message, + type = _ref.type; + var bgColor = type === 'error' ? 'bg-red-100' : type === 'success' ? 'bg-green-100' : 'bg-yellow-100'; + var textColor = type === 'error' ? 'text-red-700' : type === 'success' ? 'text-green-700' : 'text-yellow-700'; + var borderColor = type === 'error' ? 'border-red-400' : type === 'success' ? 'border-green-400' : 'border-yellow-400'; + return /*#__PURE__*/React.createElement("div", { + className: "p-4 mb-4 ".concat(bgColor, " ").concat(textColor, " ").concat(borderColor, " border-l-4 rounded-r-xl"), + role: "alert" + }, /*#__PURE__*/React.createElement("p", { + className: "font-bold" + }, type === 'error' ? 'Error' : type === 'success' ? 'Success' : 'Info'), /*#__PURE__*/React.createElement("p", null, message)); +}; +var Button = function Button(_ref2) { + var onClick = _ref2.onClick, + children = _ref2.children, + _ref2$color = _ref2.color, + color = _ref2$color === void 0 ? 'indigo' : _ref2$color; + var baseClasses = "w-full py-2 px-4 rounded-md font-medium text-white transition duration-150 ease-in-out transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2"; + var colorClasses = { + indigo: "bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500", + teal: "bg-teal-600 hover:bg-teal-700 focus:ring-teal-500", + purple: "bg-purple-600 hover:bg-purple-700 focus:ring-purple-500" + }; + return /*#__PURE__*/React.createElement("button", { + onClick: onClick, + className: "".concat(baseClasses, " ").concat(colorClasses[color]) + }, children); +}; +var CTFChallenge = function CTFChallenge() { + var _React$useState = React.useState(''), + _React$useState2 = _slicedToArray(_React$useState, 2), + username = _React$useState2[0], + setUsername = _React$useState2[1]; + var _React$useState3 = React.useState(''), + _React$useState4 = _slicedToArray(_React$useState3, 2), + password = _React$useState4[0], + setPassword = _React$useState4[1]; + var _React$useState5 = React.useState(''), + _React$useState6 = _slicedToArray(_React$useState5, 2), + newUsername = _React$useState6[0], + setNewUsername = _React$useState6[1]; + var _React$useState7 = React.useState(''), + _React$useState8 = _slicedToArray(_React$useState7, 2), + newPassword = _React$useState8[0], + setNewPassword = _React$useState8[1]; + var _React$useState9 = React.useState(''), + _React$useState10 = _slicedToArray(_React$useState9, 2), + token = _React$useState10[0], + setToken = _React$useState10[1]; + var _React$useState11 = React.useState(''), + _React$useState12 = _slicedToArray(_React$useState11, 2), + alertMessage = _React$useState12[0], + setAlertMessage = _React$useState12[1]; + var _React$useState13 = React.useState(''), + _React$useState14 = _slicedToArray(_React$useState13, 2), + alertType = _React$useState14[0], + setAlertType = _React$useState14[1]; + var alertTimerRef = React.useRef(null); + var showAlert = function showAlert(message, type) { + if (alertTimerRef.current) { + clearTimeout(alertTimerRef.current); + } + setAlertMessage(message); + setAlertType(type); + alertTimerRef.current = setTimeout(function () { + setAlertMessage(''); + setAlertType(''); + alertTimerRef.current = null; + }, 5000); + }; + var login = /*#__PURE__*/function () { + var _ref3 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee(e) { + var response, data; + return _regeneratorRuntime().wrap(function _callee$(_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + e.preventDefault(); + _context.prev = 1; + _context.next = 4; + return fetch("".concat(API_BASE_URL, "/login"), { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + username: username, + password: password + }) + }); + case 4: + response = _context.sent; + _context.next = 7; + return response.json(); + case 7: + data = _context.sent; + if (!response.ok) { + _context.next = 14; + break; + } + setToken(data.access_token); + setNewUsername(username); + showAlert('Login successful', 'success'); + _context.next = 15; + break; + case 14: + throw new Error(data.detail || 'Login failed'); + case 15: + _context.next = 20; + break; + case 17: + _context.prev = 17; + _context.t0 = _context["catch"](1); + showAlert(_context.t0.message, 'error'); + case 20: + case "end": + return _context.stop(); + } + }, _callee, null, [[1, 17]]); + })); + return function login(_x) { + return _ref3.apply(this, arguments); + }; + }(); + var updateUser = /*#__PURE__*/function () { + var _ref4 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(e) { + var response, data; + return _regeneratorRuntime().wrap(function _callee2$(_context2) { + while (1) switch (_context2.prev = _context2.next) { + case 0: + e.preventDefault(); + _context2.prev = 1; + _context2.next = 4; + return fetch("".concat(API_BASE_URL, "/update_user"), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': "Bearer ".concat(token) + }, + body: JSON.stringify({ + username: newUsername, + password: newPassword + }) + }); + case 4: + response = _context2.sent; + _context2.next = 7; + return response.json(); + case 7: + data = _context2.sent; + if (!response.ok) { + _context2.next = 13; + break; + } + setToken(data.new_token); + showAlert('User information updated successfully', 'success'); + _context2.next = 14; + break; + case 13: + throw new Error(data.detail || 'Update failed'); + case 14: + _context2.next = 19; + break; + case 16: + _context2.prev = 16; + _context2.t0 = _context2["catch"](1); + showAlert(_context2.t0.message, 'error'); + case 19: + case "end": + return _context2.stop(); + } + }, _callee2, null, [[1, 16]]); + })); + return function updateUser(_x2) { + return _ref4.apply(this, arguments); + }; + }(); + var getFlag = /*#__PURE__*/function () { + var _ref5 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee3() { + var response, data; + return _regeneratorRuntime().wrap(function _callee3$(_context3) { + while (1) switch (_context3.prev = _context3.next) { + case 0: + _context3.prev = 0; + _context3.next = 3; + return fetch("".concat(API_BASE_URL, "/get_flag"), { + headers: { + 'Authorization': "Bearer ".concat(token) + } + }); + case 3: + response = _context3.sent; + _context3.next = 6; + return response.json(); + case 6: + data = _context3.sent; + if (!response.ok) { + _context3.next = 11; + break; + } + showAlert("Flag: ".concat(data.flag), 'success'); + _context3.next = 12; + break; + case 11: + throw new Error(data.detail || 'Failed to get flag'); + case 12: + _context3.next = 17; + break; + case 14: + _context3.prev = 14; + _context3.t0 = _context3["catch"](0); + showAlert(_context3.t0.message, 'error'); + case 17: + case "end": + return _context3.stop(); + } + }, _callee3, null, [[0, 14]]); + })); + return function getFlag() { + return _ref5.apply(this, arguments); + }; + }(); + return /*#__PURE__*/React.createElement("div", { + className: "max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-xl" + }, /*#__PURE__*/React.createElement("h1", { + className: "text-2xl font-bold mb-6 text-center text-gray-800" + }, "SVUCTF Challenge"), alertMessage && /*#__PURE__*/React.createElement(Alert, { + message: alertMessage, + type: alertType + }), !token ? /*#__PURE__*/React.createElement("form", { + onSubmit: login, + className: "mb-4 space-y-4" + }, /*#__PURE__*/React.createElement("input", { + type: "text", + placeholder: "Username", + value: username, + onChange: function onChange(e) { + return setUsername(e.target.value); + }, + className: "w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + }), /*#__PURE__*/React.createElement("input", { + type: "password", + placeholder: "Password", + value: password, + onChange: function onChange(e) { + return setPassword(e.target.value); + }, + className: "w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + }), /*#__PURE__*/React.createElement(Button, { + type: "submit", + color: "indigo" + }, "Login")) : /*#__PURE__*/React.createElement("div", { + className: "space-y-4" + }, /*#__PURE__*/React.createElement("form", { + onSubmit: updateUser, + className: "space-y-4" + }, /*#__PURE__*/React.createElement("input", { + type: "text", + placeholder: "New Username", + value: newUsername, + onChange: function onChange(e) { + return setNewUsername(e.target.value); + }, + className: "w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent" + }), /*#__PURE__*/React.createElement("input", { + type: "password", + placeholder: "New Password", + value: newPassword, + onChange: function onChange(e) { + return setNewPassword(e.target.value); + }, + className: "w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent" + }), /*#__PURE__*/React.createElement(Button, { + type: "submit", + color: "teal" + }, "Update User Info")), /*#__PURE__*/React.createElement(Button, { + onClick: getFlag, + color: "purple" + }, "Get Flag"))); +}; +ReactDOM.render(/*#__PURE__*/React.createElement(CTFChallenge, null), document.getElementById('root')); diff --git a/challenges/web/python_prototype_pollution/build/app/static/index.html b/challenges/web/python_prototype_pollution/build/app/static/index.html new file mode 100644 index 0000000..762c689 --- /dev/null +++ b/challenges/web/python_prototype_pollution/build/app/static/index.html @@ -0,0 +1,18 @@ + + + + + + + SVUCTF Challenge + + + + + + +
+ + + + diff --git a/challenges/web/python_prototype_pollution/build/app/static/index.js b/challenges/web/python_prototype_pollution/build/app/static/index.js new file mode 100644 index 0000000..84e7a42 --- /dev/null +++ b/challenges/web/python_prototype_pollution/build/app/static/index.js @@ -0,0 +1,172 @@ +const API_BASE_URL = '/api'; + +const Alert = ({ message, type }) => { + const bgColor = type === 'error' ? 'bg-red-100' : type === 'success' ? 'bg-green-100' : 'bg-yellow-100'; + const textColor = type === 'error' ? 'text-red-700' : type === 'success' ? 'text-green-700' : 'text-yellow-700'; + const borderColor = type === 'error' ? 'border-red-400' : type === 'success' ? 'border-green-400' : 'border-yellow-400'; + + return ( +
+

+ {type === 'error' ? 'Error' : type === 'success' ? 'Success' : 'Info'} +

+

{message}

+
+ ); +}; + +const Button = ({ onClick, children, color = 'indigo' }) => { + const baseClasses = "w-full py-2 px-4 rounded-md font-medium text-white transition duration-150 ease-in-out transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2"; + const colorClasses = { + indigo: "bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500", + teal: "bg-teal-600 hover:bg-teal-700 focus:ring-teal-500", + }; + + return ( + + ); +}; + +const CTFChallenge = () => { + const [username, setUsername] = React.useState(''); + const [password, setPassword] = React.useState(''); + const [newUsername, setNewUsername] = React.useState(''); + const [newPassword, setNewPassword] = React.useState(''); + const [token, setToken] = React.useState(''); + const [alertMessage, setAlertMessage] = React.useState(''); + const [alertType, setAlertType] = React.useState(''); + const alertTimerRef = React.useRef(null); + + const showAlert = (message, type) => { + if (alertTimerRef.current) { + clearTimeout(alertTimerRef.current); + } + + setAlertMessage(message); + setAlertType(type); + + alertTimerRef.current = setTimeout(() => { + setAlertMessage(''); + setAlertType(''); + alertTimerRef.current = null; + }, 5000); + }; + + const login = async (e) => { + e.preventDefault(); + try { + const response = await fetch(`${API_BASE_URL}/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }), + }); + const data = await response.json(); + if (response.ok) { + setToken(data.access_token); + setNewUsername(username); + showAlert('Login successful', 'success'); + } else { + throw new Error(data.detail || 'Login failed'); + } + } catch (err) { + showAlert(err.message, 'error'); + } + }; + + const updateUser = async (e) => { + e.preventDefault(); + try { + const response = await fetch(`${API_BASE_URL}/update_user`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify({ username: newUsername, password: newPassword }), + }); + const data = await response.json(); + if (response.ok) { + setToken(data.new_token); + showAlert('User information updated successfully', 'success'); + } else { + throw new Error(data.detail || 'Update failed'); + } + } catch (err) { + showAlert(err.message, 'error'); + } + }; + + const getFlag = async () => { + try { + const response = await fetch(`${API_BASE_URL}/get_flag`, { + headers: { 'Authorization': `Bearer ${token}` }, + }); + const data = await response.json(); + if (response.ok) { + showAlert(`Flag: ${data.flag}`, 'success'); + } else { + throw new Error(data.detail || 'Failed to get flag'); + } + } catch (err) { + showAlert(err.message, 'error'); + } + }; + + return ( +
+

SVUCTF Challenge

+ + {alertMessage && } + + {!token ? ( +
+ setUsername(e.target.value)} + className="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + /> + setPassword(e.target.value)} + className="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + /> + +
+ ) : ( +
+
+ setNewUsername(e.target.value)} + className="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent" + /> + setNewPassword(e.target.value)} + className="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent" + /> + +
+ + +
+ )} +
+ ); +}; + +ReactDOM.render(, document.getElementById('root')); +