Initial commit

This commit is contained in:
Rayhan Hanaputra
2025-10-10 22:18:06 +07:00
committed by GitHub
commit ea02892f14
1246 changed files with 289342 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
.env
+41
View File
@@ -0,0 +1,41 @@
FROM python:3.9-slim
ARG PASSWORD
WORKDIR /opt
RUN apt-get update && \
apt-get install -y nano openssh-server \
gcc curl
# Create ctfuser and set password
RUN useradd -m -d /opt ctfuser && echo ctfuser:${PASSWORD} | chpasswd
COPY src/ .
# Restrict ctfuser access to only the working directory
RUN chown -R ctfuser:ctfuser /opt && \
chmod 700 /opt
# Configure SSH for ctfuser
RUN echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config && \
echo "PermitRootLogin no" >> /etc/ssh/sshd_config && \
echo "AllowUsers ctfuser" >> /etc/ssh/sshd_config
# Start SSH service
RUN ssh-keygen -A
RUN mkdir -p /run/sshd && chmod 755 /run/sshd
RUN touch /flag.txt
COPY ./flag.txt /flag.txt
COPY ./main.sh ./main.sh
RUN pip install -r requirements.txt
RUN chmod +x ./main.sh
CMD service ssh start && ./main.sh
# EXPOSE 5111
# EXPOSE 22
+27
View File
@@ -0,0 +1,27 @@
# blinkpdf
## Author
[wondPing](https://github.com/fixxall)
## Description
Lately, I have been studying signature algorithms. However, that's not enough, so I tried to make some modifications. Next, I attempted to implement it for signing a PDF document. You need the credentials user:user to Login.
attachment: https://drive.google.com/drive/folders/1u05M-eq50ZJjO7Ww4EHWBYeHJH6zyFCC?usp=sharing
## Tags
- DSA
- ECDSA
- LLL
## Exploit
This challenge contains two Attack Vector
1. Biased nonce attack on modified ECDSA
Just Login as user -> attack on sign phase -> get privatekey -> getting the flag.
solver: /solution/solver1.py
2. Biased nonce attack on DSA with waiting Flag (slow time)
Bypass Session. That was leak from token. Just generate Encrypted Flag with minimum dataset is 7*5 tick. The Basis use is 2**16. Cost time estimately (>= 35 minutes).
solver: /solution/solver2.py
Binary file not shown.
+23
View File
@@ -0,0 +1,23 @@
# BlinkPDF Patching Rule
## 1. Objective
Tujuan dari patching rule ini adalah untuk melindungi sistem dari serangan pencurian flag pada aplikasi. Patching dapat dilakukan dengan cara apapun selagi masih memenuhi peraturan pada poin 2-3 dan memperhatikan poin 4.
## 2. Allowed Patching Techniques
- **Modifikasi parameter, script, dan fungsi pada algorithm** untuk memperbaiki kerentanan pada algoritma.
## 3. Prohibited Patching Techniques
- **Penggantian atau modifikasi flow algorithm** untuk mengubah cara kerja algoritma sehingga fungsi aplikasi menjadi berubah.
- **Penyembunyian atau penggantian flag** untuk membuat flag palsu atau salah.
- **Penggunaan firewall** untuk memfilter dan membatasi lalu lintas jaringan yang mencurigakan.
## 4. Main Point
Proses patching dibebaskan dengan syarat:
- flow aplikasi masih berjalan sebagaimana mestinya
- flow algoritma masih berjalan sebagaimana mestinya (tidak mengubah algoritma yang digunakan)
- perhatikan command pada source code untuk mengetahui tempat yang dilarang untuk dilakukan modifikasi
+15
View File
@@ -0,0 +1,15 @@
version: "3"
services:
blinkpdf:
restart: always
container_name: blinkpdf
build:
context: ./
args:
- PASSWORD=PASSWORD_14000
ports:
- "14000:5111"
- "14022:22"
volumes:
- ./flag.txt:/flag.txt:ro
+1
View File
@@ -0,0 +1 @@
WRECKIT50{PLACEHOLDER}
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
/usr/sbin/sshd -D
python3 -u /opt/init.py
python3 -u /opt/app.py
+1
View File
@@ -0,0 +1 @@
__pycache__/
Binary file not shown.
+79
View File
@@ -0,0 +1,79 @@
from src.module.decdsa import *
import random
import hashlib
from sage.all import matrix, Integer, Zmod, vector
from ecdsa import NIST256p
curve = NIST256p
order = int(curve.order)
def case1():
ecdsa = DECDSA()
inc = 0
for i in range(100):
temp = random.randbytes(10)
signature = ecdsa.sign(temp)
hasil = ecdsa.verify(temp+b'1', signature)
if(not hasil):
inc += 1
print("Different message Failed:",inc)
# checking true
inc = 0
for i in range(100):
temp = random.randbytes(10)
signature = ecdsa.sign(temp)
hasil = ecdsa.verify(temp, signature)
if(not hasil):
inc += 1
print("Same message Failed:",inc)
# def Babai_closest_vector(B, target):
# # Babai's Nearest Plane algorithm
# M = B.LLL()
# G = M.gram_schmidt()[0]
# small = target
# for _ in range(1):
# for i in reversed(range(M.nrows())):
# c = ((small * G[i]) / (G[i] * G[i])).round()
# small -= M[i] * c
# return target - small
def case2():
ecdsa = ECDSA()
dataset = []
inc = 80
for i in range(inc):
temp = random.randbytes(10)
signature = ecdsa.sign(temp)
r1, r2, s = ecdsa.bytes_to_sign(signature)
message = temp
m1, m2 = message[:len(message)//2], message[len(message)//2:]
h1 = hashlib.sha256(m1).digest()
h2 = hashlib.sha256(m2).digest()
z1 = int.from_bytes(h1, byteorder='big') % order
z2 = int.from_bytes(h2, byteorder='big') % order
dataset.append([temp, z1, z2, r1, r2, s])
# B = 2**247
B = 2**201
p = order
Zn = Zmod(p)
m = [[order,0] + [-Integer((pow(dataset[i-1][5], -1, order)*(dataset[i-1][1]+dataset[i-1][2])-pow(dataset[i][5], -1, order)*(dataset[i][1]+dataset[i][2]))%order) for i in range(1, inc)]]
m += [[0,Integer(B)/order] + [Integer((pow(dataset[i-1][5], -1, order)*(dataset[i-1][3]+dataset[i-1][4])-pow(dataset[i][5], -1, order)*(dataset[i][3]+dataset[i][4]))%order) for i in range(1, inc)]]
inc -= 1
m += [[0,0]+[0]*i+[order]+[0]*(inc-i-1) for i in range(inc)]
Mat = matrix(m)
# W = Babai_closest_vector(Mat, Y)
# x = W[1] * (p-1) / 2
# print(x)
for line in Mat.LLL():
if(line[0]==order):
key = Zn(line[1]*p/B)
print(order-key, int(order).bit_length())
# is_valid = ecdsa.verify(temp, signature)
if __name__ == "__main__":
# case1()
case2()
+5
View File
@@ -0,0 +1,5 @@
http://47.129.153.66:11000;def42af0e89557cd4b2690247e3ff955e13fd8174528bc3c9a699504f0ca1dda
http://54.255.201.188:11000;90fc873d2fb07fe599baeee55508c3b20f66223017353eb1a865cf92eccfee3a
http://47.128.224.208:11000;f5c9af45ff61f8439912ad5d6d38a0cc5a0b1d9b2a9080c3bd92e4e440662643
http://13.250.57.255:11000;94fe205db665f90c5f47640e62f0f47765e56d9c2f48ce4a46a0b2cb845b2d66
http://47.129.120.12:11000;fd735f52fc4dbf641bc0392cd6834e09c9da76d917ca69d7c90e1ef4cf6ebb3e
+50
View File
@@ -0,0 +1,50 @@
import requests
from io import BytesIO
from src.module import *
port = 5111
sess = requests.Session()
# Checking C1: Login as user
url = f'http://localhost:{port}/login'
data = {'username': "user", "password": "user"}
r = sess.post(url, data=data, timeout=5)
assert 'Welcome to PDF Signature App'.lower() in r.text.lower(), 'Cannot login as user'
# print(len(pdfbytes))
PRIVATE_KEY="fc901109936ed47102fb4b6df75b2058421dfde75d8e0357f92713aace97fdf0"
# Checking C2: Sign as user
sign_url = f'http://localhost:{port}/sign'
pdfbytes = open("hello_docs.pdf","rb").read()
sendata = ('main.pdf', pdfbytes, 'application/pdf')
filedata = {'file': sendata}
r = sess.post(sign_url, files=filedata, timeout=5)
print(r.headers['Content-Type'])
signed_pdf = r.content
signed_pdf_stream = BytesIO(signed_pdf)
print(verify_signature(signed_pdf_stream, PRIVATE_KEY))
pdf_bytes_stream = BytesIO(pdfbytes)
signed_pdf_stream = sign_pdf(pdf_bytes_stream, PRIVATE_KEY)
verify_url = f'http://localhost:{port}/verify'
sendata = ('main_signed.pdf', signed_pdf_stream, 'application/pdf')
filedata = {'file': sendata}
r = sess.post(verify_url, files=filedata, timeout=5)
print('The signature is valid' in r.text)
verify_url = f'http://localhost:{port}/verify'
sendata = ('main_signed.pdf', pdfbytes, 'application/pdf')
filedata = {'file': sendata}
r = sess.post(verify_url, files=filedata, timeout=5)
# print(r.text)
print('The signature is invalid' in r.text)
url = f'http://localhost:{port}/login'
data = {'username': "admin", "password": f'{PRIVATE_KEY}'}
r = sess.post(url, data=data, timeout=5)
assert 'Welcome to PDF Signature App'.lower() in r.text.lower(), 'Cannot login as admin'
url = f'http://localhost:{port}/admin_panel'
r = sess.get(url, timeout=5)
enc_flag = r.text.split('encrypted flag: ')[1].split('</p>')[0]
print(decryptMessage(enc_flag, PRIVATE_KEY))
+165
View File
@@ -0,0 +1,165 @@
import requests
import sys
import os
import hashlib
from sage.all import matrix, Integer, Zmod, vector
# Add the parent directory to sys.path
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, parent_dir)
from src.module import *
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
import io
import PyPDF2
from src.module.decdsa import *
from ecdsa import NIST256p
curve = NIST256p
order = int(curve.order)
# checking Login
def login(userData, url):
sess = requests.Session()
r = sess.post(url+'/login', data=userData, timeout=5)
assert 'Welcome to'.lower() in r.text.lower()
return sess
# sending pdf
def sendPdf(session, pdfbytes, url):
sendata = ('main.pdf', pdfbytes, 'application/pdf')
filedata = {'file': sendata}
r = session.post(url+'/sign', files=filedata, timeout=5)
signed_pdf = r.content
signed_pdf_stream = io.BytesIO(signed_pdf)
return signed_pdf_stream
def getFlag(session, private, url):
r = session.get(url+'/admin_panel', timeout=5)
enc_flag = r.text.split("encrypted flag: ")[1].split("</p>")[0]
return decryptMessage(enc_flag, private)
# create pdf
def create_pdf_bytes(text):
buffer = io.BytesIO()
c = canvas.Canvas(buffer, pagesize=letter)
c.setTitle("Simple PDF")
c.drawString(100, 750, text)
c.showPage()
c.save()
pdf_bytes = buffer.getvalue()
buffer.close()
return pdf_bytes
# extract signature and data
def extractPdf(pdfbytes):
pdf_reader = PyPDF2.PdfReader(pdfbytes)
signature_text = pdf_reader.metadata.get('/Signature', '')
signature = bytes.fromhex(signature_text)
pdf_data = io.BytesIO()
pdf_writer = PyPDF2.PdfWriter()
for page in pdf_reader.pages:
pdf_writer.add_page(page)
pdf_writer.write(pdf_data)
pdf_data.seek(0)
pdf_content = pdf_data.read()
return pdf_content,signature
# make an dataset signature
def createDataset(session, inc, url):
ecdsa = DECDSA("ababab")
dataset = []
for i in range(inc):
temp = str(random.getrandbits(512))
pdfbytes = create_pdf_bytes(temp)
signedData = sendPdf(session, pdfbytes, url)
content, sign = extractPdf(signedData)
r1, r2, s = ecdsa.bytes_to_sign(sign)
message = content
m1, m2 = message[:len(message)//2], message[len(message)//2:]
h1 = hashlib.sha256(m1).hexdigest()[2:]
h2 = hashlib.sha256(m2).hexdigest()[2:]
z1 = int(h1, 16) % order
z2 = int(h2, 16) % order
dataset.append([temp, z1, z2, r1, r2, s])
return dataset
# attack
def biasedNonce(dataset, inc):
B = 2**251
p = order
Zn = Zmod(p)
m = [[order,0] + [-Integer((pow(dataset[i-1][5], -1, order)*(dataset[i-1][1]+dataset[i-1][2])-pow(dataset[i][5], -1, order)*(dataset[i][1]+dataset[i][2]))%order) for i in range(1, inc)]]
m += [[0,Integer(B)/order] + [Integer((pow(dataset[i-1][5], -1, order)*(dataset[i-1][3]+dataset[i-1][4])-pow(dataset[i][5], -1, order)*(dataset[i][3]+dataset[i][4]))%order) for i in range(1, inc)]]
inc -= 1
m += [[0,0]+[0]*i+[order]+[0]*(inc-i-1) for i in range(inc)]
Mat = matrix(m)
for line in Mat.LLL():
if(line[0]==order):
key = Zn(line[1]*p/B)
return order-key
def main(url):
try:
userData = {'username': "user", "password": "user"}
session = login(userData, url)
inc = 70
dataset = createDataset(session, inc, url)
print("success generate dataset:",len(dataset))
private = hex(int(biasedNonce(dataset, inc)))[2:]
adminData = {'username': "admin", "password": private}
session = login(adminData, url)
flag = getFlag(session, private, url)
return flag, private
except:
return None, None
for i in range(100):
flag, private = main('http://54.179.25.137:11000/')
print(flag)
# listu = [
# "http://47.129.153.66:11000",
# "http://54.255.201.188:11000",
# "http://47.128.224.208:11000",
# "http://13.250.57.255:11000",
# "http://47.129.120.12:11000",
# "http://52.77.232.211:11000",
# "http://13.212.110.53:11000",
# "http://54.254.138.70:11000",
# "http://54.255.228.54:11000",
# "http://13.212.239.67:11000"
# ]
# import json
# def submitFlag(flag):
# url = 'http://159.223.57.92:5000/api/flag'
# token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcyMzgxOTU5NCwianRpIjoiNTVkYzM0MjQtNzFhNy00OTU3LWFhNTItYzQyZTE4NTgzMzAxIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6eyJ1c2VybmFtZSI6InNlaGFkIn0sIm5iZiI6MTcyMzgxOTU5NCwiY3NyZiI6IjYxMjVkMWI2LTgwYTgtNDEwOC1hNjgyLTY1ZmMwMzQ5NTcwMSIsImV4cCI6MTcyMzkwNTk5NH0.r6vJbs8uxD46PSO-1tJm-zhmcDq1lk_mWQW7dICQEfU'
# headers = {
# 'Authorization': f'Bearer {token}',
# 'Content-Type': 'application/json'
# }
# data = { 'flag': flag }
# response = requests.post(url, headers=headers, data=json.dumps(data))
# return response.json()
# with open("privss.db","wb") as f:
# for url in listu:
# flag, private = main(url)
# f.write(url.encode()+b";"+private.encode()+b"\n")
# print("get url:",url, "flag:" ,flag)
# with open("privss.db","rb") as f:
# privates = f.readlines()
# for line in privates:
# try:
# url = line.decode().strip().split(";")[0]
# privkey = line.decode().strip().split(";")[1]
# adminData = {'username': "admin", "password": privkey}
# session = login(adminData, url)
# flag = getFlag(session, privkey, url)
# print("url:",url,submitFlag(flag))
# except:
# print("url:",url,"is down")
+78
View File
@@ -0,0 +1,78 @@
import requests
import sys
import os
import hashlib
from sage.all import matrix, Integer, Zmod, vector
# Add the parent directory to sys.path
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, parent_dir)
from src.module import *
private = "98388cb816ac994e5c19fd0168618f5e2e55ea2b526cf0c2bdce569235e92ead"
# This will be the base dont change it
q = 135589091449528481388008471290289910812753186702167314685052586130282290721619
p = 271178182899056962776016942580579821625506373404334629370105172260564581443239
g = pow(2, (p-1)//p, p)
# checking Login
def login(userData, url):
sess = requests.Session()
r = sess.post(url+'/login', data=userData, timeout=5)
assert 'Welcome to'.lower() in r.text.lower()
return sess
def getEncFlag(session, private, url):
r = session.get(url+'/admin_panel', timeout=5)
enc_flag = r.text.split("encrypted flag: ")[1].split("</p>")[0]
return enc_flag
def parseSignEnc(enc):
cps = bytes.fromhex(enc)
y = bytes_to_long(cps[-128:-96].lstrip(b'0'))
dig = bytes_to_long(cps[-96:-64].lstrip(b'0'))
r = bytes_to_long(cps[-64:-32].lstrip(b'0'))
s = bytes_to_long(cps[-32:].lstrip(b'0'))
return y, dig, r, s
def createDataSet(inc, session, url):
dataset = []
for i in range(inc):
enc_flag = getEncFlag(session, private, url)
dataset.append(parseSignEnc(enc_flag))
return dataset
def attackHNP(dataset):
ln = len(dataset)
X = 2**216
m = [[0]*i+[q]+[0]*(ln-i-1)+[0,0] for i in range(ln)]
m += [[(dataset[i][2]*pow(dataset[i][3], -1, q))%q for i in range(ln)] + [Integer(X)/q, 0]]
m += [[(dataset[i][1]*pow(dataset[i][3], -1, q))%q for i in range(ln)] + [0, q]]
Mat = matrix(m)
Zq = Zmod(q)
Zp = Zmod(p)
for ks in Mat.LLL().rows():
if int(ks[0])%q!=0 and dataset[0][2] == Zq(Zp(g) ** int(ks[0])):
pot = int(ks[0])%q
x = (((dataset[0][3]*pot) - dataset[0][1]) * pow(dataset[0][2], -1, q))%q
if(pot.bit_length()<216):
print("FoundX:",x)
print("FoundX:",ks[0])
return x
# return x
return 0
def main(url):
private = "98388cb816ac994e5c19fd0168618f5e2e55ea2b526cf0c2bdce569235e92ead"
print("Target:", int(private, 16))
userData = {'username': "admin", "password": private}
session = login(userData, url)
dataset = createDataSet(7 ,session, url)
if(int(private, 16)) == attackHNP(dataset):
return 0
return 1
while True:
te = main('http://localhost:5111')
if te==0: break
+116
View File
@@ -0,0 +1,116 @@
from flask import Flask, request, render_template, redirect, url_for, session, send_file
import base64
import os
from dotenv import load_dotenv
from module import sign_pdf, verify_signature, handleLogin, encryptMessage
load_dotenv()
SECRET_KEY = os.getenv('SECRET_KEY')
app = Flask(__name__)
app.config['SECRET_KEY'] = SECRET_KEY
def generate_token(username, isAdmin):
token_string = f"{username}:{SECRET_KEY}:{isAdmin}"
token = base64.b64encode(token_string.encode()).decode()
return token
def verify_token(token):
try:
decoded_token = base64.b64decode(token).decode()
username, key, isAdmin = decoded_token.split(':')
if(key==SECRET_KEY): return {'status': True, 'data': {'username':username, 'isAdmin':isAdmin=='True'}}
else: return {'status': False}
except (ValueError, TypeError):
return None
@app.route('/admin_panel')
def admin_panel():
if 'token' in session:
verifies = verify_token(session['token'])
if(verifies['status']):
decoded_token_data = verifies['data']
if decoded_token_data['isAdmin']:
FLAG = b''
try:
FLAG = open("/flag.txt","rb").read()
except:
FLAG = b'WRECKIT50{PLACEHOLDER}'
encrypted_flag = encryptMessage(FLAG, os.getenv('PRIVATE_KEY'))
return render_template('admin_panel.html', isAdmin=decoded_token_data['isAdmin'], enc_flag=encrypted_flag)
return redirect(url_for('index'))
@app.route('/')
def index():
if 'token' in session:
verifies = verify_token(session['token'])
if(verifies['status']):
decoded_token_data = verifies['data']
return render_template('index.html', isAdmin=decoded_token_data['isAdmin'])
return redirect(url_for('login'))
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
wasLogin = handleLogin(username, password)
if wasLogin['status']:
token = generate_token(username, wasLogin['isAdmin'])
session['token'] = token
return redirect(url_for('index'))
return redirect(url_for('login', error='Invalid username or password'))
return render_template('login.html')
@app.route('/logout')
def logout():
session.pop('token', None)
return redirect(url_for('index'))
@app.route('/sign', methods=['GET', 'POST'])
def sign():
if 'token' in session:
verifies = verify_token(session['token'])
if(verifies['status']):
decoded_token_data = verifies['data']
if request.method == 'POST':
if 'file' not in request.files:
return redirect(url_for('sign'), error='File not Found!')
file = request.files['file']
if file.filename == '' or not file.filename.lower().endswith('.pdf'):
return redirect(url_for('sign', error='Only PDF files are allowed.'))
signed_pdf_data = sign_pdf(file, os.getenv('PRIVATE_KEY'))
return send_file(signed_pdf_data,
mimetype='application/pdf',
as_attachment=True,
download_name=file.filename.split('.pdf')[0]+'_signed.pdf')
return render_template('sign.html', isAdmin=decoded_token_data['isAdmin'])
return redirect(url_for('login'))
@app.route('/verify', methods=['GET', 'POST'])
def verify():
if 'token' in session:
verifies = verify_token(session['token'])
if(verifies['status']):
decoded_token_data = verifies['data']
if request.method == 'POST':
if 'file' not in request.files:
return redirect(url_for('verify'), error='File not Found!')
file = request.files['file']
if file.filename == '' or not file.filename.lower().endswith('.pdf'):
return redirect(url_for('verify', error='Only PDF files are allowed.'))
is_valid = verify_signature(file, os.getenv('PRIVATE_KEY'))
return render_template('verify_result.html', is_valid=is_valid, isAdmin=decoded_token_data['isAdmin'])
return render_template('verify.html', isAdmin=decoded_token_data['isAdmin'])
return redirect(url_for('login'))
if __name__ == '__main__':
app.run(debug=True,port="5111",host="0.0.0.0")
+2
View File
@@ -0,0 +1,2 @@
YWRtaW4=;da6d6dd1e01b1ec45efb4c41260054f165c66c9a51a8ad456af03a693236018d;True
dXNlcg==;04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb;False
+24
View File
@@ -0,0 +1,24 @@
from module import register, reset
from ecdsa import NIST256p
import random
def createSecretKey():
order = NIST256p.order
keys = 0
while keys.bit_length()!=256:
keys = random.getrandbits(512) % (order)
SECRET_KEY=random.randbytes(16).hex()
PRIVATE_KEY=hex(keys)[2:]
with open(".env", "wb") as f:
f.write(f'SECRET_KEY="{SECRET_KEY}"\n'.encode())
f.write(f'PRIVATE_KEY="{PRIVATE_KEY}"\n'.encode())
return SECRET_KEY, PRIVATE_KEY
if __name__ == '__main__':
reset()
SECRET_KEY, PRIVATE_KEY = createSecretKey()
register("admin",f'{PRIVATE_KEY}',True)
register("user","user",False)
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
/usr/sbin/sshd -D
python3 -u /opt/init.py & python3 -u /opt/app.py
+1
View File
@@ -0,0 +1 @@
__pycache__/
+7
View File
@@ -0,0 +1,7 @@
from .signature import *
from .database import *
from .cipher import *
# Our checker is using decdsa.py, cipher.py and signature.py for validator. Aware when patching this 2 module,
# but you can change it if the verify and sign function work as properly.
# Adding some information, make sure PRIVATE_KEY is inside file .env, because we also check it.
+43
View File
@@ -0,0 +1,43 @@
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.number import long_to_bytes, bytes_to_long
import hashlib
import random
import os
# This will be the base dont change it
q = 135589091449528481388008471290289910812753186702167314685052586130282290721619
p = 271178182899056962776016942580579821625506373404334629370105172260564581443239
g = pow(2, (p-1)//p, p)
def encryptMessage(message, PRIVATE_KEY):
key = hashlib.sha256(bytes.fromhex(PRIVATE_KEY)).digest()[:16]
cipher = AES.new(key, AES.MODE_CBC, iv=os.urandom(16))
ciphertext = cipher.iv + cipher.encrypt(pad(message,16))
digest_message = int(hashlib.sha256(message).hexdigest(), 16)
x = int(PRIVATE_KEY, 16)
rand = random.Random()
rand.seed(bytes_to_long(message))
k = rand.getrandbits(216)
r = pow(g, k, p) % q
s = (pow(k, -1, q) * (digest_message + r * x)) % q
y = pow(g, x, q)
signature = long_to_bytes(y).zfill(32).hex() + long_to_bytes(digest_message).zfill(32).hex() + long_to_bytes(r).zfill(32).hex() + long_to_bytes(s).zfill(32).hex()
return ciphertext.hex() + signature
def decryptMessage(ciphertext, PRIVATE_KEY):
cps = bytes.fromhex(ciphertext)
y = bytes_to_long(cps[-128:-96].lstrip(b'0'))
dig = bytes_to_long(cps[-96:-64].lstrip(b'0'))
r = bytes_to_long(cps[-64:-32].lstrip(b'0'))
s = bytes_to_long(cps[-32:].lstrip(b'0'))
u = pow(s, -1, q)
v = pow(g, (dig * u) % q, p) * pow(y, (r * u)%q, p) % p % q
ciphertext = cps[:-128]
iv = ciphertext[:16]
ct = ciphertext[16:]
key = hashlib.sha256(bytes.fromhex(PRIVATE_KEY)).digest()[:16]
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
plaintext = cipher.decrypt(ct)
plain = unpad(plaintext, 16)
return v == r, plain
+29
View File
@@ -0,0 +1,29 @@
import hashlib
import base64
pathDB = 'db.db'
def handleLogin(username, password):
with open(pathDB, 'rb') as f:
listData = f.readlines()
for line in listData:
data = line.strip().split(b";")
if(base64.b64encode(username.encode())==data[0]):
if(hashlib.sha256(password.encode()).hexdigest().encode()==data[1]):
return {'status': True, 'message': 'Successfully Login', 'isAdmin': b'True'==data[2]}
else: return {'status': False, 'message': 'Wrong Password'}
return {'status': False, 'message': 'Username not found'}
def register(username, password, isAdmin):
with open(pathDB, 'ab') as f:
hashPassword = hashlib.sha256(password.encode()).hexdigest().encode()
baseUsername = base64.b64encode(username.encode())
appendData = b''
if(isAdmin): appendData += baseUsername+b';'+hashPassword+b';True\n'
else: appendData += baseUsername+b';'+hashPassword+b';False\n'
f.write(appendData)
print("Successfully append for username:",username)
def reset():
with open(pathDB, 'wb') as f:
pass
+106
View File
@@ -0,0 +1,106 @@
import hashlib
import random
from ecdsa import NIST256p, ellipticcurve
class DECDSA:
def __init__(self, privateKey):
self.curve = NIST256p
self.order = self.curve.order
self.generator = self.curve.generator
self.private_key = int(privateKey, 16) % self.order
self.public_key = self.private_key * self.generator
# self.generate_keypair()
# def generate_keypair(self):
# test
# self.private_key = 68643326375728294502573326707893599968874260096336631364679496614035223206444
# self.private_key = random.randint(1, self.order - 1)
# self.public_key = self.private_key * self.generator
def lift_x(self, x):
p = self.curve.curve._CurveFp__p
a = self.curve.curve._CurveFp__a
b = self.curve.curve._CurveFp__b
y_squared = (x**3 + a*x + b) % p
y = pow(y_squared, (p + 1) // 4, p)
if (y * y) % p != y_squared:
raise ValueError(f"No valid point found for x={x}")
point1 = ellipticcurve.Point(self.curve.curve, x, y)
point2 = ellipticcurve.Point(self.curve.curve, x, p - y)
if y > p - y:
return point2
else:
return point1
def sign(self, message):
m1, m2 = message[:len(message)//2], message[len(message)//2:]
h1 = hashlib.sha256(m1).digest()[1:] # 248bit
h2 = hashlib.sha256(m2).digest()[1:] # 248bit
z1 = int.from_bytes(h1, byteorder='big') % self.order
z2 = int.from_bytes(h2, byteorder='big') % self.order
while True:
k1 = random.randint(z1, z1*4) # 250bits
k2 = random.randint(z2, z2*4) # 250bits
# if 8 bits = 100%
# 3 just use 70 = 30-60%
R1 = k1 * self.generator
R2 = k2 * self.generator
r1 = R1.x() % self.order
r2 = R2.x() % self.order
R_att_x = (self.lift_x(r1) + self.lift_x(r2)).x() % self.order
# assert for checking valid points
if(R_att_x!=(R1+R2).x() % self.order):
continue
if r1 == 0 or r2 == 0:
continue
ks = pow(k1, -1, self.order) + pow(k2, -1, self.order)
s = (pow(k1*k2, -1, self.order) * (z1 + r1 * self.private_key + z2 + r2 * self.private_key) * pow(ks, -1, self.order)) % self.order
if s == 0:
continue
r1, r2, s = int(r1), int(r2), int(s)
return self.sign_to_bytes(r1, r2, s)
def verify(self, message, signature):
r1, r2, s = self.bytes_to_sign(signature)
if not (1 <= r1 < self.order and 1 <= r2 < self.order and 1 <= s < self.order):
return False
m1, m2 = message[:len(message)//2], message[len(message)//2:]
h1 = hashlib.sha256(m1).digest()[1:]
h2 = hashlib.sha256(m2).digest()[1:]
z1 = int.from_bytes(h1, byteorder='big') % self.order
z2 = int.from_bytes(h2, byteorder='big') % self.order
s_inv = pow(s, -1, self.order)
u1 = (z1 * s_inv) % self.order
u2 = (z2 * s_inv) % self.order
u3 = (r1 * s_inv) % self.order
u4 = (r2 * s_inv) % self.order
R = u1 * self.generator + u3 * self.public_key + u2 * self.generator + u4 * self.public_key
R_x = R.x() % self.order
R_att_x = (self.lift_x(r1) + self.lift_x(r2)).x() % self.order
return R_x == R_att_x
def long_to_bytes(self, x):
return x.to_bytes(32, "big")
def bytes_to_long(self, x):
return int.from_bytes(x, "big")
def sign_to_bytes(self, r1, r2, s):
first_part = self.long_to_bytes(r1)
second_part = self.long_to_bytes(r2)
third_part = self.long_to_bytes(s)
return first_part + second_part + third_part
def bytes_to_sign(self, x):
r1 = self.bytes_to_long(x[:32])
r2 = self.bytes_to_long(x[32:64])
s = self.bytes_to_long(x[64:])
return r1, r2, s
+42
View File
@@ -0,0 +1,42 @@
from .decdsa import DECDSA
import PyPDF2
import io
def sign_pdf(file, PRIVATE_KEY):
try:
decdsa = DECDSA(privateKey=PRIVATE_KEY)
pdf_reader = PyPDF2.PdfReader(file)
pdf_writer = PyPDF2.PdfWriter()
for page in pdf_reader.pages:
pdf_writer.add_page(page)
pdf_data = io.BytesIO()
pdf_writer.write(pdf_data)
pdf_data.seek(0)
pdf_content = pdf_data.read()
signature = decdsa.sign(pdf_content)
pdf_writer.add_metadata({'/Signature': signature.hex()})
signed_pdf = io.BytesIO()
pdf_writer.write(signed_pdf)
signed_pdf.seek(0)
return signed_pdf
except:
return False
def verify_signature(file, PRIVATE_KEY):
try:
decdsa = DECDSA(privateKey=PRIVATE_KEY)
pdf_reader = PyPDF2.PdfReader(file)
signature_text = pdf_reader.metadata.get('/Signature', '')
signature = bytes.fromhex(signature_text)
pdf_data = io.BytesIO()
pdf_writer = PyPDF2.PdfWriter()
for page in pdf_reader.pages:
pdf_writer.add_page(page)
pdf_writer.write(pdf_data)
pdf_data.seek(0)
pdf_content = pdf_data.read()
return decdsa.verify(pdf_content,signature)
except:
return False
+5
View File
@@ -0,0 +1,5 @@
flask==2.3.3
ecdsa==0.19.0
PyPDF2==3.0.1
python-dotenv==1.0.1
pycryptodome
@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blink_P!D#F</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<style>
body {
background-color: #f0f8ff;
}
.navbar {
background-color: #004080;
}
.navbar-nav a {
color: #ffffff !important;
}
.card {
margin: 20px;
}
.toast {
position: absolute;
top: 20px;
right: 20px;
z-index: 1050;
}
</style>
</head>
<body>
{% include 'menubar.html' %}
<div class="container">
<div class="row">
<div class="col-md-12">
<h1 class="mt-5">Admin Panel</h1>
<p class="lead">This is the admin panel. Manage your application here.</p>
<p class="lead">You will get the flag in encrypted.</p>
<p class="lead">The flag is encrypted using AES CBC Mode.</p>
<p class="lead">Prevent changing the algorithm for encryption because we check that.</p>
<p class="lead">You can get your encrypted flag: {{ enc_flag }}</p>
</div>
</div>
</div>
<!-- Toast notification -->
<div id="loginToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-delay="2000">
<div class="toast-header">
<strong class="mr-auto text-danger">Admin Panel Error</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body" id="toastMessage">
<!-- The error message will be inserted here -->
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>
// Display the toast if there's an error message in the query string
$(document).ready(function() {
const urlParams = new URLSearchParams(window.location.search);
const error = urlParams.get('error');
if (error) {
$('#toastMessage').text(error);
$('#loginToast').toast('show');
}
});
</script>
</body>
</html>
@@ -0,0 +1,21 @@
<!-- footer.html -->
<footer class="bg-dark text-white text-center py-4" style="padding-top:100px; margin-top:200px">
<div class="container">
<div class="d-flex justify-content-between align-items-center flex-wrap">
<!-- Created By -->
<div class="d-flex align-items-center">
<p class="mb-0 mr-3">Created by: <a href="#" class="text-white" rel="noopener">Wondping</a></p>
</div>
<div class="d-flex align-items-center">
<img src="https://cdn-icons-png.flaticon.com/512/25/25231.png" alt="GitHub Logo" class="mr-3" style="max-width: 30px;">
<p class="mb-0 mr-3"><a href="https://github.com/fixxall" class="text-white" target="_blank" rel="noopener" aria-label="Visit GitHub profile">fixxall</a></p>
</div>
<!-- Lab Partner -->
<div class="d-flex align-items-center">
<img src="http://159.223.57.92:5173/wondlab.png" alt="Lab Logo" class="mr-3" style="max-height: 30px;">
<p class="mb-0"><a href="#" class="text-white" rel="noopener">Wondlab Security</a></p>
</div>
</div>
</div>
</footer>
+169
View File
@@ -0,0 +1,169 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blink_P!D#F</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<style>
body {
background-color: #e8f5e9;
}
.navbar {
background-color: #004d40;
}
.navbar-nav a {
color: #ffffff !important;
}
.card {
border-radius: 15px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.card-body {
text-align: center;
}
.hero-image {
width: 100%;
max-height: 300px;
object-fit: cover;
border-radius: 15px;
margin-top: 20px;
}
.additional-images {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 20px;
margin: 20px 0;
}
.additional-images img {
max-width: 300px;
max-height: 200px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.gif-container {
text-align: center;
margin: 20px 0;
}
.gif-container img {
max-width: 100%;
height: auto;
}
.btn-custom {
background-color: #004d40;
color: #ffffff;
}
.btn-custom:hover {
background-color: #00332c;
}
.toast {
position: fixed;
top: 20px;
right: 20px;
z-index: 1050;
}
.toast-header {
background-color: #f8d7da;
color: #721c24;
}
.toast-body {
color: #721c24;
}
.image-logos-main {
max-width: 300px;
max-height: 300px;
margin: 20px 0;
}
.list-unstyled li {
font-size: 1.1rem;
}
</style>
</head>
<body>
{% include 'menubar.html' %}
<div class="container">
<div class="row">
<div class="col-md-12 text-center">
<h1 class="mt-5">Welcome to the PDF Signature App!</h1>
<p class="lead mt-3">
Explore our app to seamlessly sign and verify PDF files. With just a few clicks, you can ensure the authenticity and integrity of your documents. Our app offers a user-friendly interface and robust features to manage your PDF signatures effectively.
</p>
<img class="image-logos-main" src="https://external-preview.redd.it/DZRHg1I72mCNKePOqfqjkBW-7ugWeHrLHvQoerMop48.png?auto=webp&s=2059aa00df1ce0286f68ef54e573ffae680c7453" alt="PDF Signature App" class="hero-image">
<div class="gif-container">
<img src="https://media.idownloadblog.com/wp-content/uploads/2016/11/Animated-GIF-Banana.gif" alt="Interactive GIF">
</div>
<p class="mt-4">
Our PDF Signature App is designed to make document management simple and secure. Whether you need to sign documents for business purposes or verify the authenticity of received files, our app provides a comprehensive solution. With features like digital
signatures, verification checks, and a user-friendly interface, you can handle all your PDF signing needs with ease.
</p>
<p>
Explore our features to enhance your document security:
</p>
<a href="{{ url_for('sign') }}" class="btn btn-custom">Sign a PDF</a>
<a href="{{ url_for('verify') }}" class="btn btn-custom ml-2">Verify a PDF</a>
<div class="additional-images">
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQCnoxnufWEVEbp_7ehAYKyMDlgccHFFVKNnQ&s" alt="Feature 1">
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQlSnxUvz9-U6i8pam59DStrDbmFcOcdjiISQ&s" alt="Feature 2">
</div>
<ul class="list-unstyled">
<li><strong>Digital Signatures:</strong> Apply secure digital signatures to your documents.</li>
<li><strong>Verification Checks:</strong> Verify signatures to ensure document integrity.</li>
<li><strong>User-Friendly Interface:</strong> Navigate effortlessly through our intuitive design.</li>
</ul>
</div>
</div>
</div>
<!-- Footer -->
{% include 'footer.html' %}
<!-- Toast notification -->
<div id="loginToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-delay="2000">
<div class="toast-header">
<strong class="mr-auto text-danger">Login Error</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Invalid username or password. Please try again or contact support if the issue persists.
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>
// Display the toast if there's an error message in the query string
$(document).ready(function() {
const urlParams = new URLSearchParams(window.location.search);
const error = urlParams.get('error');
if (error) {
$('#loginToast').toast('show');
}
});
</script>
</body>
</html>
+122
View File
@@ -0,0 +1,122 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blink_P!D#F</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<style>
body {
background-color: #e0f7fa;
font-family: Arial, sans-serif;
}
.navbar {
background-color: #00796b;
}
.navbar-nav a {
color: #ffffff !important;
}
.card {
margin: 20px auto;
max-width: 400px;
padding: 20px;
border-radius: 10px;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
}
.card-title {
font-size: 1.5rem;
margin-bottom: 20px;
}
.btn-primary {
background-color: #00796b;
border: none;
}
.btn-primary:hover {
background-color: #004d40;
}
.toast {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1050;
}
.form-control:focus {
border-color: #00796b;
box-shadow: 0 0 0 0.2rem rgba(0, 121, 107, 0.25);
}
.login-image {
max-width: 100%;
height: auto;
margin: 20px 0;
}
</style>
</head>
<body>
{% include 'menubar.html' %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">Login</h5>
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRoMYNzuJWBFsPh7z3h_AIaYV-Htr8mnGoQTg&s" alt="Login Image" class="login-image">
<form id="loginForm" action="/login" method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" name="username" id="username" class="form-control" placeholder="Username" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" name="password" id="password" class="form-control" placeholder="Password" required>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Toast notification -->
<div id="loginToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-delay="2000">
<div class="toast-header">
<strong class="mr-auto text-danger">Login Error</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Invalid username or password.
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>
// Display the toast if there's an error message in the query string
$(document).ready(function() {
const urlParams = new URLSearchParams(window.location.search);
const error = urlParams.get('error');
if (error) {
$('#loginToast').toast('show');
}
});
</script>
</body>
</html>
@@ -0,0 +1,97 @@
<!-- menubar.html -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">
<span class="storm-icon">&#9889;</span> Blink_P!D#F
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link storm-button" href="{{ url_for('index') }}">Boarding</a>
</li>
<li class="nav-item">
<a class="nav-link storm-button" href="{{ url_for('sign') }}">Blink</a>
</li>
<li class="nav-item">
<a class="nav-link storm-button" href="{{ url_for('verify') }}">Verifiez</a>
</li>
{% if isAdmin %}
<li class="nav-item">
<a class="nav-link storm-button" href="{{ url_for('admin_panel') }}">Those'Admin?</a>
</li>
{% endif %}
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link storm-button" href="{{ url_for('logout') }}">Pulang</a>
</li>
</ul>
</div>
</nav>
<!-- Add this CSS inside <style> tags in the <head> of your HTML or in a separate CSS file -->
<style>
.storm-icon {
font-size: 1.5rem;
color: #ffcc00;
animation: lightning 1.5s infinite;
}
@keyframes lightning {
0%,
100% {
text-shadow: 0 0 5px #ffcc00, 0 0 10px #ffcc00, 0 0 15px #ffcc00;
}
50% {
text-shadow: 0 0 10px #ffcc00, 0 0 20px #ffcc00, 0 0 30px #ffcc00;
}
}
.storm-button {
position: relative;
padding: 0.5rem 1rem;
transition: color 0.3s, background-color 0.3s;
overflow: hidden;
border-radius: 0.25rem;
}
.storm-button::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 300%;
height: 300%;
background: rgba(255, 255, 255, 0.2);
transition: width 0.3s, height 0.3s, top 0.3s, left 0.3s;
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: 0;
opacity: 0;
}
.storm-button:hover::before {
width: 400%;
height: 400%;
top: -50%;
left: -50%;
opacity: 1;
}
.storm-button:hover {
color: #ffffff;
background-color: #004080;
}
.navbar-nav .nav-link {
color: #ffffff;
position: relative;
z-index: 1;
}
.navbar-nav .nav-link:hover {
color: #ffcc00;
}
</style>
+196
View File
@@ -0,0 +1,196 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blink_P!D#F</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<style>
body {
background-color: #e0f7fa;
display: flex;
flex-direction: column;
min-height: 100vh;
}
.navbar {
background-color: #00796b;
}
.navbar-nav a {
color: #ffffff !important;
}
.card {
margin: 20px;
border-radius: 10px;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
background-color: #ffffff;
}
.card-body {
padding: 20px;
width: 400px;
}
.card-title {
font-size: 1.5rem;
margin-bottom: 20px;
}
.btn-primary {
background-color: #00796b;
border: none;
}
.btn-primary:hover {
background-color: #004d40;
}
.toast {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1050;
}
.description-img {
max-width: 100%;
height: auto;
border-radius: 5px;
}
.gif-container {
text-align: center;
margin: 20px 0;
}
.gif-container img {
max-width: 150px;
height: auto;
}
.custom-description {
margin: 20px 0;
}
.chatbot-gif {
max-width: 300px;
height: auto;
border-radius: 10px;
}
.verify-container {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin: 20px 0;
}
.verify-content {
flex: 3;
margin-right: 20px;
}
.verify-form {
flex: 2;
}
.footer {
background-color: #00796b;
color: #ffffff;
text-align: center;
padding: 10px 0;
position: relative;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
{% include 'menubar.html' %}
<div class="container flex-grow-1">
<div class="verify-container">
<!-- Content on the left -->
<div class="verify-content">
<div class="custom-description">
<h4>How It Works</h4>
<p>Upload your PDF file using the form on the right. Our system will then apply a digital signature to your document. This ensures the authenticity and integrity of your PDF, providing you with a secure and verifiable document.</p>
</div>
<div class="gif-container">
<img src="https://mir-s3-cdn-cf.behance.net/project_modules/hd/0792a275536851.5c4fa1ce7f309.gif" alt="Signature Animation" class="img-fluid">
</div>
<div class="custom-description">
<h4>Why Sign Your PDF?</h4>
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTbJAS-3yVU30xEf7HBG_SZ4NAAHc4VeebXwQ&s" alt="Secure Documents" class="description-img">
<p>Adding a digital signature to your PDF ensures that the document is genuine and has not been altered. It provides a higher level of security and confidence to recipients about the authenticity of the document.</p>
</div>
</div>
<!-- Chatbot GIF on the right side -->
<div class="text-center mt-4">
<img src="https://www.aalpha.net/wp-content/uploads/2020/11/ChatBot_ace-1.gif" alt="Chatbot Animation" class="chatbot-gif">
</div>
<!-- Verification Form on the right -->
<div class="verify-form">
<div class="card">
<div class="card-body">
<h5 class="card-title">Sign PDF</h5>
<form action="/sign" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="file">Upload PDF to Sign</label>
<input type="file" name="file" class="form-control" accept=".pdf" placeholder="File upload" required>
</div>
<button type="submit" class="btn btn-primary">Sign</button>
</form>
</div>
</div>
<div class="video-container" style="padding-top:100px">
<h3>Bosen mending youtube lah:</h3>
<iframe src="https://www.youtube.com/embed/zCKQkhmW8co" title="YouTube video player" allowfullscreen></iframe>
</div>
</div>
</div>
</div>
<!-- Footer -->
{% include 'footer.html' %}
<!-- Toast notification -->
<div id="loginToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-delay="2000">
<div class="toast-header">
<strong class="mr-auto text-danger">Sign Error</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body" id="toastMessage">
<!-- The error message will be inserted here -->
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>
$(document).ready(function() {
// Display the toast if there's an error message in the query string
const urlParams = new URLSearchParams(window.location.search);
const error = urlParams.get('error');
if (error) {
$('#toastMessage').text(error);
$('#loginToast').toast('show');
}
});
</script>
</body>
</html>
+194
View File
@@ -0,0 +1,194 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blink_P!D#F</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<style>
body {
background-color: #e0f2f1;
display: flex;
flex-direction: column;
min-height: 100vh;
}
.navbar {
background-color: #00796b;
}
.navbar-nav a {
color: #ffffff !important;
}
.card {
margin: 20px;
border-radius: 10px;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
background-color: #ffffff;
}
.card-body {
padding: 20px;
}
.card-title {
font-size: 1.5rem;
margin-bottom: 20px;
}
.btn-primary {
background-color: #00796b;
border: none;
}
.btn-primary:hover {
background-color: #004d40;
}
.toast {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1050;
}
.description-img {
max-width: 100%;
height: auto;
border-radius: 5px;
}
.gif-container {
text-align: center;
margin: 20px 0;
}
.gif-container img {
max-width: 150px;
height: auto;
}
.custom-description {
margin: 20px 0;
}
.chatbot-gif {
max-width: 300px;
height: auto;
border-radius: 10px;
}
.verify-container {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin: 20px 0;
}
.verify-content {
flex: 3;
margin-right: 20px;
}
.verify-form {
flex: 2;
}
.footer {
background-color: #00796b;
color: #ffffff;
text-align: center;
padding: 10px 0;
position: relative;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
{% include 'menubar.html' %}
<div class="container flex-grow-1">
<div class="verify-container">
<!-- Content on the left -->
<div class="verify-content">
<div class="custom-description">
<h4>How It Works</h4>
<p>Upload your signed PDF file using the form on the right. Our system will then verify the digital signature on your document. This ensures that the document has not been tampered with and that the signature is valid and authentic.</p>
</div>
<div class="gif-container">
<img src="https://i.pinimg.com/originals/b9/82/77/b982770e4f0348a0a2f3b7ff25752c7b.gif" alt="Verification Animation" class="img-fluid">
</div>
<div class="custom-description">
<h4>Why Verify Your PDF?</h4>
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSvPP9W-LFQnDLmpP8qASp86qOercZI_1c64A&s" alt="Secure Documents" class="description-img">
<p>Verifying the digital signature on your PDF ensures that the document is authentic and has not been altered since it was signed. This verification process adds an extra layer of security and trust to your documents.</p>
</div>
</div>
<!-- Verification Form on the right -->
<div class="verify-form">
<div class="card">
<div class="card-body" style="width: 400px;">
<h5 class="card-title">Verify Signature</h5>
<form action="/verify" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="file">Upload Signed PDF</label>
<input type="file" name="file" class="form-control" placeholder="File upload" accept=".pdf" required>
</div>
<button type="submit" class="btn btn-primary">Verify</button>
</form>
</div>
</div>
</div>
<!-- Chatbot GIF on the right side -->
<div class="text-center mt-4">
<img src="https://www.nimbleappgenie.com/images/chatboot-banner-bottom-img.gif" alt="Chatbot Animation" class="chatbot-gif">
<div class="video-container" style="padding-top:100px">
<h3>Bosen mending youtube lah:</h3>
<iframe src="https://www.youtube.com/embed/2VALu2Tubbk" title="YouTube video player" allowfullscreen></iframe>
</div>
</div>
</div>
</div>
<!-- Footer -->
{% include 'footer.html' %}
<!-- Toast notification -->
<div id="loginToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-delay="2000">
<div class="toast-header">
<strong class="mr-auto text-danger">Verify Error</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body" id="toastMessage">
<!-- The error message will be inserted here -->
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>
$(document).ready(function() {
// Display the toast if there's an error message in the query string
const urlParams = new URLSearchParams(window.location.search);
const error = urlParams.get('error');
if (error) {
$('#toastMessage').text(error);
$('#loginToast').toast('show');
}
});
</script>
</body>
</html>
@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blink_P!D#F</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<style>
body {
background-color: #f0f8ff;
}
.navbar {
background-color: #004080;
}
.navbar-nav a {
color: #ffffff !important;
}
.card {
margin: 20px;
border-radius: 15px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.card-body {
text-align: center;
}
.result-image {
max-width: 300px;
max-height: 300px;
margin: 20px 0;
}
.btn-custom {
background-color: #004080;
color: #ffffff;
}
.btn-custom:hover {
background-color: #003366;
}
.toast {
position: absolute;
top: 20px;
right: 20px;
z-index: 1050;
}
.toast-header {
background-color: #f8d7da;
color: #721c24;
}
.toast-body {
color: #721c24;
}
</style>
</head>
<body>
{% include 'menubar.html' %}
<div class="container">
<div class="card">
<div class="card-body">
<h5 class="card-title">Verification Result</h5>
{% if is_valid %}
<img src="https://kompak.or.id/wp-content/uploads/2024/01/A1-Valid-Artinya-Dalam-Kepolisian-Bahasa-Gaul.jpg" alt="Valid Signature" class="result-image">
<p class="card-text text-success">The signature is <strong>valid</strong>.</p>
{% else %}
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRupVYejUUjYB61R0y1c8NnA18NgBkuv8pz0A&s" alt="Invalid Signature" class="result-image">
<p class="card-text text-danger">The signature is <strong>invalid</strong>.</p>
{% endif %}
<a href="{{ url_for('verify') }}" class="btn btn-custom">Go Back</a>
</div>
</div>
</div>
<!-- Toast notification -->
<div id="loginToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-delay="2000">
<div class="toast-header">
<strong class="mr-auto text-danger">Result Error</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body" id="toastMessage">
<!-- The error message will be inserted here -->
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>
// Display the toast if there's an error message in the query string
$(document).ready(function() {
const urlParams = new URLSearchParams(window.location.search);
const error = urlParams.get('error');
if (error) {
$('#toastMessage').text(error);
$('#loginToast').toast('show');
}
});
</script>
</body>
</html>
+102
View File
@@ -0,0 +1,102 @@
version: '3.1'
services:
# --- poke ---
poke:
container_name: poke_container
hostname: poke
restart: always
build:
context: poke
args:
- PASSWORD=$PASSWORD_10000
volumes:
- ../receiver/flags/poke.txt:/flag.txt:ro
- ../utils/bashrc:/root/.bashrc:ro
- ../utils/preexec.sh:/root/.preexec.sh:ro
ports:
- "10000:80"
- "10022:22"
extra_hosts:
- "host.docker.internal:host-gateway"
# --- poke ---
# --- blinkpdf ---
blinkpdf:
container_name: blinkpdf_container
hostname: blinkpdf
restart: always
build:
context: blinkpdf
args:
- PASSWORD=$PASSWORD_11000
volumes:
- ../receiver/flags/blinkpdf.txt:/flag.txt:ro
- ../utils/bashrc:/root/.bashrc:ro
- ../utils/preexec.sh:/root/.preexec.sh:ro
ports:
- "11000:5111"
- "11022:22"
extra_hosts:
- "host.docker.internal:host-gateway"
# --- blinkpdf ---
# --- naraka ---
naraka:
container_name: naraka_container
hostname: naraka
restart: always
build:
context: naraka
args:
- PASSWORD=$PASSWORD_12000
volumes:
- ../receiver/flags/naraka.txt:/flag.txt:ro
- ../utils/bashrc:/root/.bashrc:ro
- ../utils/preexec.sh:/root/.preexec.sh:ro
ports:
- "12000:5000"
- "12022:22"
extra_hosts:
- "host.docker.internal:host-gateway"
# --- naraka ---
# --- wanderer ---
wanderer:
container_name: wanderer_container
hostname: wanderer
restart: always
build:
context: wanderer
args:
- PASSWORD=$PASSWORD_13000
volumes:
- ../receiver/flags/wanderer.txt:/flag.txt:ro
- ../utils/bashrc:/root/.bashrc:ro
- ../utils/preexec.sh:/root/.preexec.sh:ro
ports:
- "13000:80"
- "13022:22"
extra_hosts:
- "host.docker.internal:host-gateway"
# --- wanderer ---
# --- niko ---
niko:
container_name: niko_container
hostname: niko
restart: always
build:
context: niko
args:
- PASSWORD=$PASSWORD_15000
volumes:
- ../receiver/flags/niko.txt:/flag.txt:ro
- ../utils/bashrc:/root/.bashrc:ro
- ../utils/preexec.sh:/root/.preexec.sh:ro
ports:
- "15000:8000"
- "15022:22"
extra_hosts:
- "host.docker.internal:host-gateway"
# --- niko ---
+42
View File
@@ -0,0 +1,42 @@
FROM python:3.9-slim
ARG PASSWORD
WORKDIR /app
RUN apt-get update && \
apt-get install -y nano openssh-server \
gcc curl
# Create ctfuser and set password
RUN useradd -m -d /app ctfuser && echo ctfuser:${PASSWORD} | chpasswd
# Configure SSH for ctfuser
RUN echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config && \
echo "PermitRootLogin no" >> /etc/ssh/sshd_config && \
echo "AllowUsers ctfuser" >> /etc/ssh/sshd_config
# Start SSH service
RUN ssh-keygen -A
RUN mkdir -p /run/sshd && chmod 755 /run/sshd
RUN apt update
RUN apt install python3-pip -y
RUN pip3 install flask==3.0.2 --break-system-packages
COPY /src .
COPY flag.txt ../
# Restrict ctfuser access to only the working directory
RUN chown -R ctfuser:ctfuser /app/scripts && \
chmod 700 /app/scripts
# Restrict ctfuser access to only the working directory
RUN chown -R ctfuser:ctfuser /app/satanize.py && \
chmod 700 /app/satanize.py
COPY start.sh .
RUN chmod +x start.sh
# Start SSH service as root and then switch to ctfuser
CMD service ssh start && ./start.sh
+15
View File
@@ -0,0 +1,15 @@
version: "3"
services:
naraka:
restart: always
container_name: naraka
build:
context: ./
args:
- PASSWORD=PASSWORD_12000
ports:
- "12000:5000"
- "12022:22"
# volumes:
# - ./flag.txt:/flag.txt:ro
+1
View File
@@ -0,0 +1 @@
Th1s_15_y0Ur_Fl4G
+1
View File
@@ -0,0 +1 @@
Flask==3.0.2
+77
View File
@@ -0,0 +1,77 @@
from flask import Flask, request, render_template_string, render_template
import subprocess
import os
import satanize
app = Flask(__name__)
# Load flag content
FLAG = open('../flag.txt', 'r').read().strip()
@app.route('/', methods=['GET', 'POST'])
def index():
return render_template("index.html", result="")
@app.route('/chall', methods=['POST'])
def chall():
userinput = ""
name = ""
if request.method == 'POST':
challindex = request.form['chall']
if challindex != '1' and challindex != '2':
return render_template("index.html", result="")
if challindex == '1':
script = "scripts/execute.py"
desc = "It will execute every single line code (Math, Logical check, Concat String, Statistics): a = 5;b = 4;print(a+b), x = 23;y = 23;print(x==y), a = 'The'; b = 'demon'; print(a+b), x = [1,2,3,4,5,6,7,8];print(max(x)), etc"
elif challindex == '2':
script = "scripts/evaluate.py"
desc = "It will calculate your sins: 1+1, 2*2, 5-2, etc"
try:
userinput = request.form['input']
except Exception as e:
return render_template("chall.html", challindex = challindex, result = "", desc=desc)
if(userinput != ""):
try:
FLAG = open('../flag.txt', 'r').read().strip()
result = subprocess.check_output(['python', script, userinput, FLAG])
print(result)
except subprocess.CalledProcessError as e:
result = e.output.decode()
return render_template("chall.html", challindex = challindex, result=result, desc = desc)
else:
return render_template("chall.html", challindex = challindex, result = "", desc = desc)
@app.route('/render', methods=['GET'])
def render():
with open('templates/template.html', 'r') as file:
template = file.read()
name = request.args.get('name')
if name != "":
try:
stn = satanize.Satanize()
if(stn.satanizer(name)):
name = "Bad boy"
return render_template_string(template.replace("thisistemplate",name))
except Exception as e:
pass
return render_template_string(template.replace("thisistemplate",request.args.get('name')))
else:
return "Hello, please send me your 'name'"
@app.route('/sourcecode/<challindex>', methods=['GET'])
def sourcecode(challindex):
if challindex == '1':
sc = "scripts/execute.py"
elif challindex == '2':
sc = "scripts/evaluate.py"
elif challindex == '3':
sc = "satanize.py"
# Read the content of script.py
with open(sc, 'r') as script_file:
script_content = script_file.read()
return render_template("source.html", script_content=script_content)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=1)
Binary file not shown.
+12
View File
@@ -0,0 +1,12 @@
class Satanize:
def __init__(self):
FLAG = open('../flag.txt', 'r').read().strip()
self.banned_words = ["7","import","system"]
# self.banned_words = ""
def satanizer(self, text):
for word in self.banned_words:
if word in text:
print("SATANNNNNNNN")
return True
return False
+19
View File
@@ -0,0 +1,19 @@
import sys
import string
BLACKLIST ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
# BLACKLIST = ""
# You cannot change the value of FLAG, FLAG can only be filled with sys.argv[2]
FLAG = sys.argv[2]
def security_check(user_input, blacklist):
for bl in blacklist:
if bl in user_input:
return 1
if __name__ == "__main__":
user_input = sys.argv[1]
if(security_check(user_input,BLACKLIST)):
print("too bad")
else:
print(eval(user_input))
+23
View File
@@ -0,0 +1,23 @@
import sys
import string
BLACKLIST =['FLAG','cat']
# BLACKLIST = ""
# You cannot change the value of FLAG, FLAG can only be filled with sys.argv[2]
FLAG = sys.argv[2]
def security_check(user_input, blacklist):
for bl in blacklist:
if bl in user_input:
return 1
if __name__ == "__main__":
user_input = sys.argv[1]
if(security_check(user_input,BLACKLIST)):
print("too bads")
else:
try:
user_input = eval(user_input)
except Exception as e:
pass
exec(user_input)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

+47
View File
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>It is not Jail, it is Hellllll</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet" />
<style>
/* Animasi flicker */
.flicker {
animation: flickerAnimation 0.1s infinite;
}
@keyframes flickerAnimation {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
</style>
</head>
<body class="bg-black text-red-500 min-h-screen flex flex-col justify-center items-center flicker">
<div class="w-full max-w-md p-8 bg-gray-800 rounded-lg shadow-lg">
<h1 class="text-4xl font-bold text-center mb-8 text-red-600">It is not Jail, it is Hellllll</h1>
<a>{{ desc }}</a>
<form method="POST" action="/chall" class="space-y-4">
<div>
<label for="input" class="block text-lg font-medium text-red-500">Input:</label>
<input type="text" id="input" name="input" class="w-full p-2 rounded bg-gray-700 text-white focus:outline-none focus:ring-2 focus:ring-red-600" required />
</div>
<input type="text" id="chall" name="chall" value="{{challindex}}" hidden />
<button type="submit" class="w-full p-3 mt-4 bg-red-600 hover:bg-red-700 text-white rounded font-bold">Submit</button>
</form>
<a href="/"><button class="w-full p-3 mt-4 bg-black hover:bg-gray-700 text-red rounded font-bold">Back</button></a>
<h2 class="text-2xl font-semibold mt-8 text-red-500">Output:</h2>
<pre class="bg-gray-900 p-4 rounded text-white mt-2">{{ result }}</pre>
<a href="/sourcecode/{{challindex}}" class="block mt-6 text-center text-red-400 hover:text-red-500 underline">Source code</a>
</div>
<!-- Include footer -->
{% include 'footer.html' %}
</body>
</html>
@@ -0,0 +1,7 @@
<footer class="bg-red-900 text-white py-4 mt-10">
<div class="container mx-auto text-center">
<p>Made with ❤️ by AODreamer</p>
</div>
</footer>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More