Java Eigenes Captcha implementieren, um Kontaktinfos zu schützen

n/a

Banned
Registriert
Feb. 2025
Beiträge
673
Guten Tag,

ich möchte mein eigenes Captcha verwenden und wollte fragen, ob die folgende Umsetzung in Ordnung wäre, bevor ich es online nehme, oder ob es dabei sicherheitstechnische Bedenken gäbe

Main.java:

Java:
import com.hellokaton.blade.Blade;
import java.awt.*;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Base64;
import java.util.HashMap;
import javax.imageio.ImageIO;
import net.logicsquad.nanocaptcha.content.LatinContentProducer;
import net.logicsquad.nanocaptcha.image.ImageCaptcha;
import net.logicsquad.nanocaptcha.image.backgrounds.GradiatedBackgroundProducer;
import net.logicsquad.nanocaptcha.image.noise.CurvedLineNoiseProducer;
import net.logicsquad.nanocaptcha.image.renderer.DefaultWordRenderer;
import org.json.JSONObject;

public class Main {
  public static void main(String[] args) {
    final HashMap<String, Long> lastAccess = new HashMap<>();
    final HashMap<Integer, String> map = new HashMap<>();
    Blade.create()
        .get(
            "/get",
            ctx -> {
              long now = System.currentTimeMillis();
              String clientIp = ctx.address();
              System.out.println("Client IP: " + clientIp + ", Request Time: " + now);
              if (lastAccess.containsKey(clientIp)) {
                long last = lastAccess.get(clientIp);
                if (now - last <= 10_000) {
                  lastAccess.put(clientIp, now);

                  JSONObject response = new JSONObject();
                  response.put("ok", false);
                  response.put(
                      "message", "Please wait 10 seconds before requesting a new captcha.");
                  ctx.status(403);
                  ctx.json(response.toString());
                  return;
                }
              }
              lastAccess.put(clientIp, now);

              ImageCaptcha ic =
                  new ImageCaptcha.Builder(500, 150)
                      .addContent(
                          new LatinContentProducer(12),
                          new DefaultWordRenderer.Builder()
                              .randomColor(Color.BLACK, Color.BLUE, Color.CYAN, Color.RED)
                              .build())
                      .addBackground(new GradiatedBackgroundProducer())
                      .addNoise(new CurvedLineNoiseProducer())
                      .build();
              String code = ic.getContent();
              RenderedImage img = ic.getImage();
              String base64Image = imgToBase64String(img, "PNG");
              int hash = base64Image.hashCode();
              map.put(hash, code);

              JSONObject response = new JSONObject();
              response.put("ok", true);
              response.put("base64Image", base64Image);
              ctx.json(response.toString());

              System.out.println("Generated captcha with hash: " + hash + " and code: " + code);
            })
        .get(
            "/check/:hash/:code",
            ctx -> {
              long now = System.currentTimeMillis();
              String clientIp = ctx.address();
              System.out.println("Client IP: " + clientIp + ", Request Time: " + now);
              if (lastAccess.containsKey(clientIp)) {
                long last = lastAccess.get(clientIp);
                if (now - last <= 10_000) {
                  lastAccess.put(clientIp, now);

                  JSONObject response = new JSONObject();
                  response.put("ok", false);
                  response.put("message", "Please wait 10 seconds before checking a new captcha.");
                  ctx.status(403);
                  ctx.json(response.toString());
                  return;
                }
              }
              lastAccess.put(clientIp, now);
              try {
                int hash = ctx.pathInt("hash");
                String code = ctx.pathString("code");
                System.out.println("Checking captcha with hash: " + hash + " and code: " + code);
                if (map.containsKey(hash)) {
                  String expectedCode = map.get(hash);
                  if (expectedCode.equals(code)) {
                    JSONObject response = new JSONObject();
                    response.put("ok", true);
                    response.put("message", "Captcha is correct.");
                    response.put(
                        "secret_message",
                        "This message contains the secret information you requested.");
                    ctx.json(response.toString());
                  } else {
                    JSONObject response = new JSONObject();
                    response.put("ok", false);
                    response.put("message", "Captcha is incorrect.");
                    ctx.status(403);
                    ctx.json(response.toString());
                  }
                } else {
                  JSONObject response = new JSONObject();
                  response.put("ok", false);
                  response.put("message", "Captcha not found or expired.");
                  ctx.status(403);
                  ctx.json(response.toString());
                }
              } catch (Exception e) {
                JSONObject response = new JSONObject();
                response.put("ok", false);
                response.put("message", "Invalid request.");
                ctx.status(400);
                ctx.json(response.toString());
              }
            })
        .get(
            "/demo",
            ctx -> {
              long now = System.currentTimeMillis();
              String clientIp = ctx.address();
              System.out.println("Client IP: " + clientIp + ", Request Time: " + now);
              ctx.render("demo.html");
            })
        .listen(80)
        .start();
  }

  public static String imgToBase64String(final RenderedImage img, final String formatName) {
    final ByteArrayOutputStream os = new ByteArrayOutputStream();
    try {
      ImageIO.write(img, formatName, os);
      return Base64.getEncoder().encodeToString(os.toByteArray());
    } catch (final IOException ioe) {
      throw new UncheckedIOException(ioe);
    }
  }
}

templates/demo.html:

HTML:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Demo</title>
</head>

<body>
    <input type="button" value="Request a new captcha" onclick="requestCaptcha();">
    <div id="captchaContainer">
        <p>Captcha will be displayed here after request.</p>
    </div>
    <label for="captchaInput">Captcha text:</label>
    <input type="text" id="captchaInput" placeholder="Enter captcha here">
    <input type="button" value="Submit captcha" onclick="submitCaptcha();">
    <script>
        let hash = null;

        function hashString(str) {
            let hash = 0, l = str.length, i = 0;
            if (l > 0)
                while (i < l)
                    hash = (hash << 5) - hash + str.charCodeAt(i++) | 0;
            return hash;
        }

        async function requestCaptcha() {
            await fetch('/get')
                .then(data => data.json())
                .then(json => {
                    if (!json.ok) {
                        throw new Error(json.message || 'Failed to fetch captcha');
                    }
                    let captchaContainer = document.getElementById('captchaContainer');
                    captchaContainer.innerHTML = ''; // Clear previous content
                    let img = document.createElement('img');
                    img.src = 'data:image/png;base64,' + json.base64Image;
                    img.alt = 'Captcha Image';
                    img.style.paddingTop = '1rem';
                    captchaContainer.appendChild(img);
                    hash = hashString(json.base64Image); // Store the hash of the image
                })
                .catch(error => {
                    let captchaContainer = document.getElementById('captchaContainer');
                    let errorMessage = document.createElement('p');
                    errorMessage.textContent = 'Error fetching captcha: ' + (error.message || 'Unknown error');
                    captchaContainer.appendChild(errorMessage);
                });
        }

        async function submitCaptcha() {
            let captchaText = document.getElementById('captchaInput').value;
            await fetch('/check/' + hash + '/' + encodeURIComponent(captchaText))
                .then(data => data.json())
                .then(json => {
                    if (!json.ok) {
                        throw new Error(json.message || 'Failed to fetch captcha');
                    }
                    let captchaContainer = document.getElementById('captchaContainer');
                    captchaContainer.innerHTML = ''; // Clear previous content
                    let resultMessage = document.createElement('p');
                    resultMessage.textContent = json.message || 'Captcha submitted successfully!';
                    captchaContainer.appendChild(resultMessage);
                    let secretMessage = document.createElement('p');
                    secretMessage.textContent = json.secret_message || 'No secret message provided.';
                    captchaContainer.appendChild(secretMessage);
                    hash = null; // Reset hash after submission
                })
                .catch(error => {
                    let captchaContainer = document.getElementById('captchaContainer');
                    let errorMessage = document.createElement('p');
                    errorMessage.textContent = 'Error submitting captcha: ' + (error.message || 'Unknown error');
                    captchaContainer.appendChild(errorMessage);
                });
        }
    </script>
</body>

</html>

Dependencies:

Code:
dependencies {
    implementation 'org.apache.commons:commons-lang3:3.18.0'
    implementation 'org.json:json:20250517'
    implementation 'com.hellokaton:blade-core:2.1.2.RELEASE'
    implementation 'net.logicsquad:nanocaptcha:2.1'
    implementation 'org.slf4j:slf4j-simple:2.0.17'
}

Nun kann man den Captcha-Server starten und http://localhost/demo aufrufen... Möglicherweise geht das nur unter Linux

Es geht darum, dass ich einige Kontaktinfos durch ein Captcha vor bösen Robotern schützen möchte - und nur Menschen diese einsehen können sollen

Damit nicht ständig ein neues Captcha genriert werden kann, habe ich das auf 10 Sekunden pro IP begrenzt

Kann man das so online stellen oder ergeben sich hier Angriffspunkte?

Wenn Bedarf besteht, könnte ich auch noch ein https://de.wikipedia.org/wiki/Sequenzdiagramm beifügen
 
Du musst immer aufpassen bei hashmap und Konsorten bzgl. Kollisionen und der Größe der Datenstruktur.
DDos mit ipv6 dürfte negative Auswirkungen haben.

Deine alle 10s protection ist imho ein Problem fur eine andere Ebene, WAF o.Ä., aber wenn du soweit gehst dass du das brauchst, dann kannst auch gleich cloudflare einsetzen, die lösen das für dich (inkl captcha).

Ohne zu wissen für was du es einsetzen willst, bzw was du meinst mit Kontaktinfos schützen ist das alles Glaskugel
 
  • Gefällt mir
Reaktionen: n/a und madmax2010
Häng da cloudflare vor. Selbst implementierte captchas und gerade visuelle captchas bekommt man heutzutage leicht umgangen
 
madmax2010 schrieb:
captchas bekommt man heutzutage leicht umgangen
Ja, aber wer sich diesen Aufwand macht, der darf von mir aus auch an die Information gelangen

Es geht darum, eine Kontakt-E-Mail vor neugierigen Blicken zu schützen

An DDoS usw. hatte ich auch schon gedacht, deshalb verwende ich zusätzlich die HTTP-Status-Codes... Dann sollte eigentlich fail2ban nach 50 Versuchen eingreifen
 
n/a schrieb:
Ja, aber wer sich diesen Aufwand macht, der darf von mir aus auch an die Information gelangen

Es geht darum, eine Kontakt-E-Mail vor neugierigen Blicken zu schützen
Warum machst du es dir dann so kompliziert und erfindest das Rad neu? Da gibt es in jeder Sprache libs für
n/a schrieb:
An DDoS usw. hatte ich auch schon gedacht, deshalb verwende ich zusätzlich die HTTP-Status-Codes... Dann sollte eigentlich fail2ban nach 50 Versuchen eingreifen
Das erste d sieht das anders
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: BeBur und floq0r
n/a schrieb:
Es geht darum, dass ich einige Kontaktinfos durch ein Captcha vor bösen Robotern schützen möchte - und nur Menschen diese einsehen können sollen
Nur vorsichtshalber: Impressumsangaben dürfen nicht durch ein captcha geschützt werden.
n/a schrieb:
Damit nicht ständig ein neues Captcha genriert werden kann, habe ich das auf 10 Sekunden pro IP begrenzt
Ich muss 10 Sekunden warten, wenn ich mich vertippt habe? Das würde mich sehr, sehr nerven.
n/a schrieb:
Es geht darum, eine Kontakt-E-Mail vor neugierigen Blicken zu schützen
Du könntest stattdessen ein Web-Forumular anbieten, deine E-Mail ist dann für den Nutzer gar nicht erst sichtbar.

Wenn ich sowas selber implementieren wollen würde würde ich das vermutlich versuchen so einfach wie möglich zu halten. Meine Webseite ist <www.fahrrad-shop360grad.de> - Mein Captcha fragt den Nutzer "Was verkaufen wir hier vornehmlich? Tipp: 'F*hrr*der'. Oder "Wie viel Grad hat unser Shop? Tipp: "359+1".
Das ganze lebt ja nicht davon, dass die technische Maßnahme das höchste Niveau hat, sondern dass Crawler das nicht automatisiert hinbekommen, weil es eine Eigenlösung ist. Darf halt kein normales/sinnvolles html sein das captcha.
 
  • Gefällt mir
Reaktionen: Guru-Meditation und madmax2010
BeBur schrieb:
Du könntest stattdessen ein Web-Forumular anbieten, deine E-Mail ist dann für den Nutzer gar nicht erst sichtbar.

Ich locke in ein Honeypot, wer kleben bleibt wird geblockt und direkt ins Nirvana geleitet. Funktioniert bei meinen Projekten seit Jahren zuverlässig. Captcha ist meiner Meinung nach ein Benutzerfreundlichkeitskiller, manche übertreiben meiner Meinung nach..aber jedem das Seine.
 
  • Gefällt mir
Reaktionen: BeBur
Zurück
Oben