diff options
| -rw-r--r-- | README.md | 38 | ||||
| -rw-r--r-- | get.html | 131 | ||||
| -rw-r--r-- | index.html | 143 | ||||
| -rw-r--r-- | paste-cgi.py | 142 | ||||
| -rw-r--r-- | script.js | 230 | 
5 files changed, 302 insertions, 382 deletions
| @@ -1,7 +1,7 @@  # Paste-cgi  ## Description -This is a dirty pastebin done in CGI. It is completely client-side encrypted and server only stored encrypted paste. +This is a dirty pastebin done in CGI. It is completely client-side encrypted and server only stores encrypted paste(Title is not encrypted).  ## Images   @@ -10,24 +10,44 @@ This is a dirty pastebin done in CGI. It is completely client-side encrypted and  ## Nginx Config  ```  server { +    server_name paste.example.com; +    root /example/root/dir; -    server_name SERVERURL; -    root ROOTFOLDER; +    # Global basic Auth  +    auth_basic "Restricted Content"; +    auth_basic_user_file /etc/nginx/.htpasswd; -    location / { -        auth_basic "Restricted Content"; # Basic Auth -        auth_basic_user_file /etc/nginx/.htpasswd; # Basic Auth +    # Server static files +    location = / { +        try_files /index.html =404; +    } + +    location = /get { +        try_files /get.html =404; +    } + +    location ~* \.(css|js|svg)$ { +        try_files $uri =404; +        access_log off; +        expires 30d; +    } + +    # Handle cgi routes +    location ~ ^/(paste|submit)$ {          include fastcgi_params; -        fastcgi_param SCRIPT_FILENAME PATH TO SCRIPT; +        fastcgi_param SCRIPT_FILENAME /example/path/to/cgi;          fastcgi_param PATH_INFO $uri;          fastcgi_param QUERY_STRING $args;          fastcgi_param HTTP_HOST $server_name;          fastcgi_param CONTENT_LENGTH $content_length;          fastcgi_param CONTENT_TYPE $content_type; -        fastcgi_param ALLOWED_DIR PATH WHERE THE ALLOWED DIRECTORY IS TO CHANGE FILES; +        fastcgi_param ALLOWED_DIR /example/allowed/dir;          fastcgi_pass unix:/run/fcgiwrap.socket;      } - +    # Deny everything else +    location / { +        return 403; +    }      listen [::]:443 ssl; # managed by Certbot      listen 443 ssl; # managed by Certbot @@ -3,15 +3,16 @@  <head>      <meta charset="UTF-8"> -    <meta name="description" content="AES-256 Encryption Example"> -    <title>Secure Paste</title> +    <title>paste-cgi</title>      <link rel="icon" href="./favicon.svg" type="image/svg+xml">      <link rel="stylesheet" href="./pico.min.css">  </head>  <body>      <main class="container"> -        <h1>paste-cgi</h1> +        <a href="/"> +            <h1>paste-cgi</h1> +        </a>          <form id="cryptoForm">              <h3 id="title">Title</h3> @@ -30,129 +31,7 @@              <button type="button" onclick="copyPaste()">Copy</button>          </div>      </main> - -    <script> -        let globalData; -        const pasteTypeConstant = "pasteType"; -        const passwordFieldConstant = "passwordField"; -        const passwordTypeConstant = "password"; -        const decryptButtonConstant = "decryptBtn"; -        const plainTypeConstant = "plain"; -        const titleConstant = "title"; -        const expirationConstant = "expiration"; -        const plaintextConstant = "plaintext"; -        const decryptedOutputConstant = "decryptedOutput" -        const pasteUrlSectionConstant = "pasteUrlSection" - -        window.onload = async function () { -            const urlParams = new URLSearchParams(window.location.search); -            const id = urlParams.get('id'); - -            const fetchUrl = `${window.location.origin}/paste?id=${id}`; - -            try { -                const response = await fetch(fetchUrl); - -                if (!response.ok) { -                    throw new Error('Failed to fetch data'); -                } - -                const data = await response.json(); - -                globalData = data; -                togglePasteType() -            } catch (error) { -                alert("NO File Found") -                console.error('Error fetching data:', error); -            } -        }; - -        function copyPaste() { -            var copyText = document.getElementById(decryptedOutputConstant); - -            copyText.select(); -            copyText.setSelectionRange(0, 99999); - -            navigator.clipboard.writeText(copyText.value); -        } -        function togglePasteType() { -            const decryptButton = document.getElementById(decryptButtonConstant); -            const passwordField = document.getElementById(passwordFieldConstant); -            document.getElementById(titleConstant).textContent = globalData.title; -            if (globalData.type === passwordTypeConstant) { -                passwordField.style.display = 'block'; -                decryptButton.style.display = 'block'; -            } else { -                decryptButton.style.display = 'none'; -                passwordField.style.display = 'none'; -                document.getElementById(decryptedOutputConstant).textContent = globalData.pasted_text -                document.getElementById(pasteUrlSectionConstant).style.display = 'block'; -            } -        } -        async function deriveKey(password, salt) { -            let encodedPassword = new TextEncoder().encode(password); -            let baseKey = await window.crypto.subtle.importKey( -                "raw", -                encodedPassword, -                { name: "PBKDF2" }, -                false, -                ["deriveKey"], -            ); - -            let derivedKey = await window.crypto.subtle.deriveKey( -                { -                    name: "PBKDF2", -                    salt: salt, -                    iterations: 600000, -                    hash: "SHA-256", -                }, -                baseKey, -                { name: "AES-GCM", length: 256 }, -                true, -                ["encrypt", "decrypt"], -            ); - -            return derivedKey; -        } - -        async function decryptData(encryptedData, password) { -            let { ciphertext, iv, authTag, salt } = encryptedData; -            let key = await deriveKey(password, salt); - -            let dataWithAuthTag = new Uint8Array(ciphertext.length + authTag.length); -            dataWithAuthTag.set(ciphertext, 0); -            dataWithAuthTag.set(authTag, ciphertext.length); - -            let decryptedContent = await window.crypto.subtle.decrypt( -                { name: "AES-GCM", iv: iv, tagLength: 128 }, -                key, -                dataWithAuthTag, -            ); - -            return new TextDecoder().decode(decryptedContent); -        } -        async function handleDecrypt() { -            let base64 = globalData.pasted_text; -            let password = document.getElementById(passwordTypeConstant).value; -            if (!base64 || !password) return alert("Enter encrypted text and password."); -            try { - -                let parsed = JSON.parse(atob(base64)); -                let encryptedData = { -                    salt: new Uint8Array(Object.values(parsed.salt)), -                    iv: new Uint8Array(Object.values(parsed.iv)), -                    authTag: new Uint8Array(Object.values(parsed.authTag)), -                    ciphertext: new Uint8Array(Object.values(parsed.ciphertext)), -                }; - -                let decrypted = await decryptData(encryptedData, password); -                document.getElementById("decryptedOutput").value = decrypted; -                document.getElementById(pasteUrlSectionConstant).style.display = 'block'; -            } catch (err) { -                document.getElementById("decryptedOutput").value = "❌ Decryption failed."; -            } -        } -    </script> +    <script src="/script.js"></script>  </body>  </html>
\ No newline at end of file @@ -3,15 +3,16 @@  <head>    <meta charset="UTF-8"> -  <meta name="description" content="AES-256 Encryption Example"> -  <title>Secure Paste</title> +  <title>paste-cgi</title>    <link rel="icon" href="./favicon.svg" type="image/svg+xml">    <link rel="stylesheet" href="./pico.min.css">  </head>  <body>    <main class="container"> -    <h1>paste-cgi</h1> +    <a href="/"> +      <h1>paste-cgi</h1> +    </a>      <form id="cryptoForm">        <label for="title">Title</label> @@ -54,141 +55,7 @@        <button type="button" onclick="copyPaste()">Copy</button>      </div>    </main> - -  <script> -    const pasteTypeConstant = "pasteType"; -    const passwordFieldConstant = "passwordField"; -    const passwordTypeConstant = "password"; -    const plainTypeConstant = "plain"; -    const titleConstant = "title"; -    const expirationConstant = "expiration"; -    const plaintextConstant = "plaintext"; -    const pasteUrlConstant = "pasteUrl" -    const pasteUrlSectionConstant = "pasteUrlSection" - -    function copyPaste(){ -      var copyText = document.getElementById(pasteUrlConstant); - -      copyText.select(); -      copyText.setSelectionRange(0, 99999); - -      navigator.clipboard.writeText(copyText.value); -    } - -    function togglePasteType() { -      const passwordType = document.getElementById(pasteTypeConstant).value; -      const passwordField = document.getElementById(passwordFieldConstant); -      if (passwordType === passwordTypeConstant) { -        passwordField.style.display = 'block'; -      } else { -        passwordField.style.display = 'none'; -      } -    } - -    async function deriveKey(password, salt) { -      let encodedPassword = new TextEncoder().encode(password); -      let baseKey = await window.crypto.subtle.importKey( -        "raw", -        encodedPassword, -        { name: "PBKDF2" }, -        false, -        ["deriveKey"], -      ); - -      let derivedKey = await window.crypto.subtle.deriveKey( -        { -          name: "PBKDF2", -          salt: salt, -          iterations: 600000, -          hash: "SHA-256", -        }, -        baseKey, -        { name: "AES-GCM", length: 256 }, -        true, -        ["encrypt", "decrypt"], -      ); - -      return derivedKey; -    } - -    async function encryptData(data, password) { -      let salt = window.crypto.getRandomValues(new Uint8Array(16)); -      let iv = window.crypto.getRandomValues(new Uint8Array(12)); -      let key = await deriveKey(password, salt); -      let encodedData = new TextEncoder().encode(data); - -      let encryptedContent = await window.crypto.subtle.encrypt( -        { -          name: "AES-GCM", -          iv: iv, -          tagLength: 128, -        }, -        key, -        encodedData, -      ); - -      let ciphertext = encryptedContent.slice( -        0, -        encryptedContent.byteLength - 16, -      ); -      let authTag = encryptedContent.slice(encryptedContent.byteLength - 16); - -      return { -        ciphertext: new Uint8Array(ciphertext), -        iv: iv, -        authTag: new Uint8Array(authTag), -        salt: salt, -      }; -    } -     -    async function handlePaste() { -      let type = document.getElementById(pasteTypeConstant).value; -      let title = document.getElementById(titleConstant).value; -      let expiration = document.getElementById(expirationConstant).value; -      let plaintext = document.getElementById(plaintextConstant).value; -      let password = document.getElementById(passwordTypeConstant).value; - -      let pasted_text = "" -      if (type == plainTypeConstant) { -        if (!plaintext || !title) return alert("Enter title and paste."); - -        pasted_text = plaintext -      } -      else { -        if (!plaintext || !password || !title) return alert("Enter title, paste and password."); - -        let encrypted = await encryptData(plaintext, password); -        pasted_text = btoa(JSON.stringify(encrypted)) -      } - -      let currentPath = window.location.origin; - -      try { -        let response = await fetch(currentPath + "/submit", { -          method: "POST", -          headers: { -            "Content-Type": "application/json", -          }, -          body: JSON.stringify({ -            type: type, -            title: title, -            expiration: expiration, -            pasted_text: pasted_text -          }), -        }); - -        if (response.ok) { -          let jsonResponse = await response.json(); -          document.getElementById(pasteUrlConstant).value = currentPath + "/get?id=" + jsonResponse.id; -          document.getElementById(pasteUrlSectionConstant).style.display = 'block'; -        } else { -          console.error("Failed to submit data. Status:", response.status); -        } -      } catch (error) { -        console.error("Error making the POST request:", error); -      } -    } -  </script> +  <script src="/script.js"></script>  </body>  </html>
\ No newline at end of file diff --git a/paste-cgi.py b/paste-cgi.py index 88bd802..dd1d9e1 100644 --- a/paste-cgi.py +++ b/paste-cgi.py @@ -46,13 +46,33 @@ def check_working_dir(allowed_dir):          sys.exit(0) +def status_405(): +    print("Status: 405 Method Not Allowed") +    print("Content-Type: text/plain") +    print("") +    print("405 Method Not Allowed") +    sys.exit(0) + + +def status_415(): +    print("Status: 415 Unsupported Media Type") +    print("Content-Type: text/plain") +    print("") +    print("415 Unsupported Media Type.") +    sys.exit(0) + + +def status_404(): +    print("Status: 404 Not Found") +    print("Content-Type: text/plain") +    print("") +    print("File not found") +    sys.exit(0) + +  def check_method(method):      if method not in ["GET", "POST"]: -        print("Status: 405 Method Not Allowed") -        print("Content-Type: text/plain") -        print("") -        print("405 Method Not Allowed") -        sys.exit(0) +        status_405()  def get_content_lenght(): @@ -82,59 +102,7 @@ def validate_payload(payload):              ):                  if SubmitConstants.PASTED_TEXT in payload:                      return -    print("Status: 415 Unsupported Media Type") -    print("Content-Type: text/plain") -    print("") -    print("415 Unsupported Media Type: Expected 'application/json'.") -    sys.exit(0) - - -def return_index_html(): -    try: -        with open("index.html", "r") as file: -            index_html = file.read() - -        print("Content-Type: text/html") -        print("") -        print(index_html) - -    except Exception: -        print("Status: 404 Not Found") -        print("Content-Type: text/html") -        print("") -        print("<html><body><h1>404 Not Found</h1></body></html>") - - -def return_pico_css(): -    try: -        with open("pico.min.css", "r") as file: -            pico_css = file.read() - -        print("Content-Type: text/css") -        print("") -        print(pico_css) - -    except Exception: -        print("Status: 404 Not Found") -        print("Content-Type: text/html") -        print("") -        print("<html><body><h1>404 Not Found</h1></body></html>") - - -def return_favicon_svg(): -    try: -        with open("favicon.svg", "r") as file: -            favicon_svg = file.read() - -        print("Content-Type: image/svg+xml") -        print("") -        print(favicon_svg) - -    except Exception: -        print("Status: 404 Not Found") -        print("Content-Type: text/html") -        print("") -        print("<html><body><h1>404 Not Found</h1></body></html>") +    status_415()  def submit(post_data): @@ -203,17 +171,11 @@ def handle_data(data):  def return_paste(query_string):      if query_string is None or not query_string.startswith("id="): -        print("Content-Type: text/plain") -        print("") -        print(query_string) -        sys.exit(0) +        status_415()      id = query_string.split("=")      if len(id) != 2: -        print("Content-Type: text/plain") -        print("") -        print(query_string) -        sys.exit(0) +        status_415()      id = id[1] @@ -222,18 +184,10 @@ def return_paste(query_string):      os.chdir(directory)      if os.getcwd() != DATABASE_DIRECTORY: -        print("Content-Type: text/plain") -        print("") -        print("NICE TRY") -        sys.exit(0) +        status_415()      if not os.path.exists(full_path): -        print("Status: 404 Not Found") -        print("Content-Type: text/plain") -        print("") -        print("File not found") -        print(full_path) -        sys.exit(0) +        status_404()      with open(full_path, "r") as file:          data = json.load(file) @@ -243,10 +197,7 @@ def return_paste(query_string):          if deleted:              os.remove(full_path) -            print("Status: 404 Not Found") -            print("Content-Type: text/plain") -            print("") -            print("File not found") +            status_404()          print("Content-Type: application/json")          print("") @@ -257,26 +208,7 @@ def return_paste(query_string):          sys.exit(0)      except Exception: -        print("Status: 404 Not Found") -        print("Content-Type: text/plain") -        print("") -        print("File not found") - - -def return_get(): -    try: -        with open("get.html", "r") as file: -            get_html = file.read() - -        print("Content-Type: text/html") -        print("") -        print(get_html) - -    except Exception: -        print("Status: 404 Not Found") -        print("Content-Type: text/html") -        print("") -        print("<html><body><h1>404 Not Found</h1></body></html>") +        status_404()  allowed_dir = os.environ.get("ALLOWED_DIR", None) @@ -293,15 +225,7 @@ content_length = get_content_lenght()  if method == "GET": -    if script_name == "/": -        return_index_html() -    elif script_name == "/pico.min.css": -        return_pico_css() -    elif script_name == "/favicon.svg": -        return_favicon_svg() -    elif script_name == "/get": -        return_get() -    elif script_name == "/paste": +    if script_name == "/paste":          return_paste(query_string)  elif method == "POST":      post_data = sys.stdin.read(content_length) diff --git a/script.js b/script.js new file mode 100644 index 0000000..995156f --- /dev/null +++ b/script.js @@ -0,0 +1,230 @@ +const pasteTypeConstant = "pasteType"; +const passwordFieldConstant = "passwordField"; +const passwordTypeConstant = "password"; +const plainTypeConstant = "plain"; +const titleConstant = "title"; +const expirationConstant = "expiration"; +const plaintextConstant = "plaintext"; +const pasteUrlConstant = "pasteUrl"; +const pasteUrlSectionConstant = "pasteUrlSection"; +const decryptedOutputConstant = "decryptedOutput"; +const decryptButtonConstant = "decryptBtn"; + +let globalData; + +function copyPaste() { +    const copyText = document.getElementById( +        document.getElementById(pasteUrlConstant) +            ? pasteUrlConstant +            : decryptedOutputConstant +    ); +    copyText.select(); +    copyText.setSelectionRange(0, 99999); +    navigator.clipboard.writeText(copyText.value); +} + +function togglePasteType() { +    const passwordType = document.getElementById(pasteTypeConstant)?.value || globalData?.type; +    const passwordField = document.getElementById(passwordFieldConstant); +    const decryptButton = document.getElementById(decryptButtonConstant); + +    if (document.getElementById(titleConstant) && globalData?.title) { +        document.getElementById(titleConstant).textContent = "Title:" + globalData.title; +    } + +    if (passwordType === passwordTypeConstant) { +        passwordField.style.display = "block"; +        if (decryptButton) decryptButton.style.display = "block"; +    } else { +        passwordField.style.display = "none"; +        if (decryptButton) decryptButton.style.display = "none"; +        if (document.getElementById(decryptedOutputConstant)) { +            document.getElementById(decryptedOutputConstant).textContent = globalData?.pasted_text || ""; +        } +        document.getElementById(pasteUrlSectionConstant).style.display = "block"; +    } +} + +async function deriveKey(password, salt) { +    const encodedPassword = new TextEncoder().encode(password); +    const baseKey = await window.crypto.subtle.importKey( +        "raw", +        encodedPassword, +        { name: "PBKDF2" }, +        false, +        ["deriveKey"] +    ); + +    const derivedKey = await window.crypto.subtle.deriveKey( +        { +            name: "PBKDF2", +            salt: salt, +            iterations: 600000, +            hash: "SHA-256", +        }, +        baseKey, +        { name: "AES-GCM", length: 256 }, +        true, +        ["encrypt", "decrypt"] +    ); + +    return derivedKey; +} + +async function encryptData(data, password) { +    const salt = window.crypto.getRandomValues(new Uint8Array(16)); +    const iv = window.crypto.getRandomValues(new Uint8Array(12)); +    const key = await deriveKey(password, salt); +    const encodedData = new TextEncoder().encode(data); + +    const encryptedContent = await window.crypto.subtle.encrypt( +        { +            name: "AES-GCM", +            iv: iv, +            tagLength: 128, +        }, +        key, +        encodedData +    ); + +    const ciphertext = encryptedContent.slice(0, encryptedContent.byteLength - 16); +    const authTag = encryptedContent.slice(encryptedContent.byteLength - 16); + +    return { +        ciphertext: new Uint8Array(ciphertext), +        iv: iv, +        authTag: new Uint8Array(authTag), +        salt: salt, +    }; +} + +async function decryptData(encryptedData, password) { +    const { ciphertext, iv, authTag, salt } = encryptedData; +    const key = await deriveKey(password, salt); + +    const dataWithAuthTag = new Uint8Array(ciphertext.length + authTag.length); +    dataWithAuthTag.set(ciphertext, 0); +    dataWithAuthTag.set(authTag, ciphertext.length); + +    const decryptedContent = await window.crypto.subtle.decrypt( +        { name: "AES-GCM", iv: iv, tagLength: 128 }, +        key, +        dataWithAuthTag +    ); + +    return new TextDecoder().decode(decryptedContent); +} + +async function handlePaste() { +    const type = document.getElementById(pasteTypeConstant).value; +    const title = document.getElementById(titleConstant).value; +    const expiration = document.getElementById(expirationConstant).value; +    const plaintext = document.getElementById(plaintextConstant).value; +    const password = document.getElementById(passwordTypeConstant).value; + +    let pasted_text = ""; + +    if (type === plainTypeConstant) { +        if (!plaintext || !title) return alert("Enter title and paste."); +        pasted_text = plaintext; +    } else { +        if (!plaintext || !password || !title) return alert("Enter title, paste and password."); +        const encrypted = await encryptData(plaintext, password); +        pasted_text = btoa(JSON.stringify(encrypted)); +    } + +    const currentPath = window.location.origin; + +    try { +        const response = await fetch(currentPath + "/submit", { +            method: "POST", +            headers: { +                "Content-Type": "application/json", +            }, +            body: JSON.stringify({ +                type: type, +                title: title, +                expiration: expiration, +                pasted_text: pasted_text, +            }), +        }); + +        if (response.ok) { +            const jsonResponse = await response.json(); +            document.getElementById(pasteUrlConstant).value = currentPath + "/get?id=" + jsonResponse.id; +            document.getElementById(pasteUrlSectionConstant).style.display = "block"; +        } else { +            console.error("Failed to submit data. Status:", response.status); +        } +    } catch (error) { +        console.error("Error making the POST request:", error); +    } +} + +async function handleDecrypt() { +    const base64 = globalData.pasted_text; +    const password = document.getElementById(passwordTypeConstant).value; +    if (!base64 || !password) return alert("Enter encrypted text and password."); + +    try { +        const parsed = JSON.parse(atob(base64)); +        const encryptedData = { +            salt: new Uint8Array(Object.values(parsed.salt)), +            iv: new Uint8Array(Object.values(parsed.iv)), +            authTag: new Uint8Array(Object.values(parsed.authTag)), +            ciphertext: new Uint8Array(Object.values(parsed.ciphertext)), +        }; + +        const decrypted = await decryptData(encryptedData, password); +        document.getElementById(decryptedOutputConstant).value = decrypted; +        document.getElementById(pasteUrlSectionConstant).style.display = "block"; +    } catch (err) { +        document.getElementById(decryptedOutputConstant).value = "❌ Decryption failed."; +    } +} + +async function loadPasteFromUrl() { +    const urlParams = new URLSearchParams(window.location.search); +    const id = urlParams.get("id"); + +    if (!id) return; + +    const fetchUrl = `${window.location.origin}/paste?id=${id}`; + +    try { +        const response = await fetch(fetchUrl); +        if (!response.ok) throw new Error("Failed to fetch data"); + +        globalData = await response.json(); +        togglePasteType(); +    } catch (error) { +        alert("No File Found"); +        console.error("Error fetching data:", error); +    } +} + +window.onload = async function () { +    let path = window.location.pathname; +    if (path != "/get") +        return; +    const urlParams = new URLSearchParams(window.location.search); +    const id = urlParams.get('id'); + +    const fetchUrl = `${window.location.origin}/paste?id=${id}`; + +    try { +        const response = await fetch(fetchUrl); + +        if (!response.ok) { +            throw new Error('Failed to fetch data'); +        } + +        const data = await response.json(); + +        globalData = data; +        togglePasteType() +    } catch (error) { +        alert("No File Found") +        console.error('Error fetching data:', error); +    } +};
\ No newline at end of file | 
