removed old files
This commit is contained in:
@@ -1,41 +0,0 @@
|
||||
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
|
||||
@@ -1,27 +0,0 @@
|
||||
# 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
|
||||
Vendored
BIN
Binary file not shown.
Vendored
-23
@@ -1,23 +0,0 @@
|
||||
# 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
|
||||
@@ -1,15 +0,0 @@
|
||||
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 +0,0 @@
|
||||
WRECKIT50{PLACEHOLDER}
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
/usr/sbin/sshd -D
|
||||
|
||||
python3 -u /opt/init.py
|
||||
python3 -u /opt/app.py
|
||||
@@ -1 +0,0 @@
|
||||
__pycache__/
|
||||
Binary file not shown.
@@ -1,79 +0,0 @@
|
||||
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()
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
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
|
||||
@@ -1,50 +0,0 @@
|
||||
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))
|
||||
@@ -1,165 +0,0 @@
|
||||
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")
|
||||
@@ -1,78 +0,0 @@
|
||||
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
|
||||
@@ -1,116 +0,0 @@
|
||||
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")
|
||||
@@ -1,2 +0,0 @@
|
||||
YWRtaW4=;da6d6dd1e01b1ec45efb4c41260054f165c66c9a51a8ad456af03a693236018d;True
|
||||
dXNlcg==;04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb;False
|
||||
@@ -1,24 +0,0 @@
|
||||
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)
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
/usr/sbin/sshd -D
|
||||
python3 -u /opt/init.py & python3 -u /opt/app.py
|
||||
@@ -1 +0,0 @@
|
||||
__pycache__/
|
||||
@@ -1,7 +0,0 @@
|
||||
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.
|
||||
@@ -1,43 +0,0 @@
|
||||
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
|
||||
@@ -1,29 +0,0 @@
|
||||
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
|
||||
@@ -1,106 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
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
|
||||
@@ -1,5 +0,0 @@
|
||||
flask==2.3.3
|
||||
ecdsa==0.19.0
|
||||
PyPDF2==3.0.1
|
||||
python-dotenv==1.0.1
|
||||
pycryptodome
|
||||
@@ -1,81 +0,0 @@
|
||||
<!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">×</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>
|
||||
@@ -1,21 +0,0 @@
|
||||
<!-- 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>
|
||||
@@ -1,169 +0,0 @@
|
||||
<!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">×</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>
|
||||
@@ -1,122 +0,0 @@
|
||||
<!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">×</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>
|
||||
@@ -1,97 +0,0 @@
|
||||
<!-- menubar.html -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="#">
|
||||
<span class="storm-icon">⚡</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>
|
||||
@@ -1,196 +0,0 @@
|
||||
<!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">×</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>
|
||||
@@ -1,194 +0,0 @@
|
||||
<!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">×</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>
|
||||
@@ -1,114 +0,0 @@
|
||||
<!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">×</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>
|
||||
@@ -1,10 +1,10 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
web:
|
||||
blogpost:
|
||||
build: .
|
||||
container_name: ctf_web
|
||||
container_name: blogpost_container
|
||||
ports:
|
||||
- "4413:8000"
|
||||
- "10000:8000"
|
||||
environment:
|
||||
- FLASK_ENV=production
|
||||
command: ["/app/entrypoint.sh"]
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
cdn:
|
||||
build: .
|
||||
container_name: cdn_container
|
||||
ports:
|
||||
- "4414:8000"
|
||||
- "11000:8000"
|
||||
environment:
|
||||
SECRET_KEY: "c75f1259a4c95bb31563405d488d7bf9c0eaf4d562fd13557624f8e18eb5cfff"
|
||||
RESEED_FLAG: "1"
|
||||
|
||||
+15
-75
@@ -1,17 +1,17 @@
|
||||
version: '3.1'
|
||||
|
||||
services:
|
||||
# --- poke ---
|
||||
poke:
|
||||
container_name: poke_container
|
||||
hostname: poke
|
||||
# --- blogpost ---
|
||||
blogpost:
|
||||
container_name: blogpost_container
|
||||
hostname: blogpost
|
||||
restart: always
|
||||
build:
|
||||
context: poke
|
||||
context: blogpost
|
||||
args:
|
||||
- PASSWORD=$PASSWORD_10000
|
||||
volumes:
|
||||
- ../receiver/flags/poke.txt:/flag.txt:ro
|
||||
- ../receiver/flags/blogpost.txt:/flag.txt:ro
|
||||
- ../utils/bashrc:/root/.bashrc:ro
|
||||
- ../utils/preexec.sh:/root/.preexec.sh:ro
|
||||
ports:
|
||||
@@ -19,84 +19,24 @@ services:
|
||||
- "10022:22"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
# --- poke ---
|
||||
# --- blogpost ---
|
||||
|
||||
# --- blinkpdf ---
|
||||
blinkpdf:
|
||||
container_name: blinkpdf_container
|
||||
hostname: blinkpdf
|
||||
# --- cdn ---
|
||||
cdn:
|
||||
container_name: cdn_container
|
||||
hostname: cdn
|
||||
restart: always
|
||||
build:
|
||||
context: blinkpdf
|
||||
context: cdn
|
||||
args:
|
||||
- PASSWORD=$PASSWORD_11000
|
||||
volumes:
|
||||
- ../receiver/flags/blinkpdf.txt:/flag.txt:ro
|
||||
- ../receiver/flags/cdn.txt:/flag.txt:ro
|
||||
- ../utils/bashrc:/root/.bashrc:ro
|
||||
- ../utils/preexec.sh:/root/.preexec.sh:ro
|
||||
ports:
|
||||
- "11000:5111"
|
||||
- "11000:80"
|
||||
- "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 ---
|
||||
# --- cdn ---
|
||||
@@ -1,42 +0,0 @@
|
||||
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
|
||||
@@ -1,15 +0,0 @@
|
||||
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 +0,0 @@
|
||||
Th1s_15_y0Ur_Fl4G
|
||||
@@ -1 +0,0 @@
|
||||
Flask==3.0.2
|
||||
Binary file not shown.
@@ -1,77 +0,0 @@
|
||||
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.
@@ -1,12 +0,0 @@
|
||||
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
|
||||
@@ -1,19 +0,0 @@
|
||||
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))
|
||||
@@ -1,23 +0,0 @@
|
||||
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.
|
Before Width: | Height: | Size: 1.7 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 MiB |
@@ -1,47 +0,0 @@
|
||||
<!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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user