Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Same captcha image returned ten times consecutively under circumstances #145

Closed
Korkman opened this issue Feb 24, 2022 · 8 comments · Fixed by #148
Closed

Same captcha image returned ten times consecutively under circumstances #145

Korkman opened this issue Feb 24, 2022 · 8 comments · Fixed by #148
Milestone

Comments

@Korkman
Copy link
Contributor

Korkman commented Feb 24, 2022

The librecaptcha server can end up in a state where images are reused consecutively, ten times each. This allows an attacker to multiply the payout for solving a captcha (open ten tabs in advance, then solve the captcha, copy & paste the result into other tabs).

This script should put the server in said state using brute force, but be aware this happens randomly on production servers without high load.

#! /bin/sh
{
instance="localhost:8888"

echo "launching highly parallel http requests on $instance"
echo "press ENTER to continue"
read bogus

echo "exhausting pre-generated captchas round #1"
for p in $(seq 1 20)
do
	(for i in $(seq 1 50)
	do
		curl --silent -X POST -d '{ "level": "easy", "input_type": "text", "media": "image/gif" }' $instance/v1/captcha > /dev/null
	done) &
done

wait

echo "please observe librecaptcha CPU usage. press ENTER after usage drops close to zero."
read bogus

echo "exhausting pre-generated captchas round #2"
for p in $(seq 1 20)
do
	(for i in $(seq 1 50)
	do
		curl --silent -X POST -d '{ "level": "easy", "input_type": "text", "media": "image/gif" }' $instance/v1/captcha > /dev/null
	done) &
done

wait

echo "done. visit http://$instance/demo/index.html and reqest"
echo "easy, image/gif, text captchas. they will be 10x the same."

}

config.json:

{
  "randomSeed" : -101132924,
  "port" : 8888,
  "address" : "0.0.0.0",
  "captchaExpiryTimeLimit" : 5,
  "throttle" : 1000,
  "threadDelay" : 2,
  "playgroundEnabled" : true,
  "corsHeader" : "",
  "captchas" : [ {
    "name" : "FilterChallenge",
    "allowedLevels" : [ "medium", "hard" ],
    "allowedMedia" : [ "image/png" ],
    "allowedInputType" : [ "text" ],
    "config" : { }
  }, {
    "name" : "PoppingCharactersCaptcha",
    "allowedLevels" : [ "hard" ],
    "allowedMedia" : [ "image/gif" ],
    "allowedInputType" : [ "text" ],
    "config" : { }
  }, {
    "name" : "ShadowTextCaptcha",
    "allowedLevels" : [ "easy" ],
    "allowedMedia" : [ "image/png" ],
    "allowedInputType" : [ "text" ],
    "config" : { }
  }, {
    "name" : "RainDropsCaptcha",
    "allowedLevels" : [ "easy", "medium" ],
    "allowedMedia" : [ "image/gif" ],
    "allowedInputType" : [ "text" ],
    "config" : { }
  } ]
}
@hrj
Copy link
Contributor

hrj commented Feb 24, 2022

Perfect! This gives me some ideas about what the problem is and how to fix it. I think the background task needs to "load balance" the captcha types, so that each enabled type has sufficient captchas in queue at all times.

In the meanwhile, here's a workaround: if your app only uses one or two types of captcha, consider enabling only those in the config file. You can just delete the other entries. The background task will then create throttle (=1000) challenges of the configured types, and the server won't be starved of captchas for those types any more.

@Korkman
Copy link
Contributor Author

Korkman commented Feb 24, 2022

Seems to be something else. I just tried that, removing all but the RainDropsCaptcha, and the script still produces the bugged state. It's not just about exhaustion.

{
  "randomSeed" : -101132924,
  "port" : 8888,
  "address" : "0.0.0.0",
  "captchaExpiryTimeLimit" : 5,
  "throttle" : 100,
  "threadDelay" : 2,
  "playgroundEnabled" : true,
  "corsHeader" : "",
  "captchas" : [ {
    "name" : "RainDropsCaptcha",
    "allowedLevels" : [ "easy", "medium" ],
    "allowedMedia" : [ "image/gif" ],
    "allowedInputType" : [ "text" ],
    "config" : { }
  } ]
}

NOTE: I reduced throttle from 1000 to 100 here, because the DB grew too big for my taste. I noticed the server recovers at some point. Up until then, the DB is inflated. I guess it's the 5 minute captchaExpiryTimeLimit, at which old captcha images are purged? Is the total amount of captcha images is limited to the throttle value?

@hrj
Copy link
Contributor

hrj commented Feb 25, 2022

Seems to be something else. I just tried that, removing all but the RainDropsCaptcha, and the script still produces the bugged state.

I forgot to mention: you need to delete the current DB. Else, the older entries will not be purged. To delete the DB, issue rm -rf data/H2.

I noticed the server recovers at some point. Up until then, the DB is inflated. I guess it's the 5 minute captchaExpiryTimeLimit, at which old captcha images are purged? Is the total amount of captcha images is limited to the throttle value?

It's a little hard to explain. We should document this in a wiki. Also the config names are not very intuitive. After I finish the code changes for this issue, I will try to rename the config parameters and write a wiki page explaining them and the overall server architecture.

@Korkman
Copy link
Contributor Author

Korkman commented Feb 25, 2022

"allowedLevels" : [ "easy", "medium" ],

I missed limiting this to the used value "easy". But I can still starve the server easily (using the script) and it will show the bugged behavior (because the displayed captchas need to stay saved until they expire or are validated). What helps, then, is setting maxAttempts = 1 (feature available in master branch), which makes the server generate a new captcha on every hit when starved. This should be the default because IMHO re-using captchas is not a good idea, even when picked randomly from the list. It allow an attacker to create a list of solutions for shown captchas and match them automatically. Even when set to > 1, it should be forced to 1 internally when the available pool size is proportionally too small to get enough randomness.

@hrj
Copy link
Contributor

hrj commented Feb 25, 2022

Even when set to > 1, it should be forced to 1 internally when the available pool size is proportionally too small to get enough randomness.

I agree, it should be proportionate to the pool size. Instead of an integer count it should probably be a fractional multiplier (0.0 to 1.0 with an absolute minimum of 1). The default can be a small fraction such as 0.01.

This will allow the site owner to increase the pool size based on traffic, without worrying about changing maxAttempts also. I will create a separate issue for that.

@hrj
Copy link
Contributor

hrj commented Feb 27, 2022

@Korkman

This script should put the server in said state using brute force, but be aware this happens randomly on production servers without high load.

I have made a tentative fix in the fix145 branch in this repo. With your script, I can't reproduce this issue on that branch. Can you please try and confirm the fix?

@hrj hrj closed this as completed in #148 Mar 1, 2022
hrj added a commit that referenced this issue Mar 1, 2022
Fix for #145: frequent repetition of CAPTCHA challenge
@Korkman
Copy link
Contributor Author

Korkman commented Mar 1, 2022

@hrj: Thanks for the fix. Unfortunately, I can't build right now, because

[info] loading settings for project root from build.sbt ...
[info] set current project to LibreCaptcha (in build file:/build/)
[warn]
[warn]  Note: Unresolved dependencies path:
[error] sbt.librarymanagement.ResolveException: Error downloading org.scala-lang:scala-library:3.1.1
[error]   Not found
[error]   Not found
[error]   not found: /root/.ivy2/local/org.scala-lang/scala-library/3.1.1/ivys/ivy.xml
[error]   not found: https://repo1.maven.org/maven2/org/scala-lang/scala-library/3.1.1/scala-library-3.1.1.pom
[error] Error downloading org.json4s:json4s-jackson_3.1:4.0.4
[error]   Not found
[error]   Not found
[error]   not found: /root/.ivy2/local/org.json4s/json4s-jackson_3.1/4.0.4/ivys/ivy.xml
[error]   not found: https://repo1.maven.org/maven2/org/json4s/json4s-jackson_3.1/4.0.4/json4s-jackson_3.1-4.0.4.pom

Freshly cloned master and "docker build". Any env variables I need to tweak?

Edit: more lines of error message

@hrj
Copy link
Contributor

hrj commented Mar 1, 2022

@Korkman That sounds like a transient error on your side. The CI is building fine: https://github.com/librecaptcha/lc-core/actions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants