Add README.md

This commit is contained in:
fluepke 2020-12-04 05:01:20 +01:00
commit e2904e8cb4
No known key found for this signature in database
GPG key ID: 37E30BD2FBE7746A
3 changed files with 168 additions and 0 deletions

71
README.md Normal file
View file

@ -0,0 +1,71 @@
# Vodafone Station Exporter
Prometheus Exporter for the Vodafone Station CGA4233DE.
## Reverse Engineering the login mechanism
> I am not a Javascript engineer, but it works :man_shrugging:
Logging into the PHP application running on the CGA4233DE is made as complicated as possible.
From the console we see:
```bash
curl 'http://192.168.100.1/api/v1/session/login' \
-H 'Connection: keep-alive' \
-H 'Accept: */*' \
-H 'X-CSRF-TOKEN: ' \
-H 'X-Requested-With: XMLHttpRequest' \
-H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
--data-raw 'username=admin&password=seeksalthash' \
--compressed \
--insecure
```
> CSRF seems broken, lol. Whatever - we don't care.
reply is
```json
{"error":"ok","salt":"<something>","saltwebui":"<something_else>"}
```
For the actual login a derived token derived from the actual password is used:
```
curl 'http://192.168.100.1/api/v1/session/login' \
-H 'Connection: keep-alive' \
-H 'Accept: */*' \
-H 'X-CSRF-TOKEN: ' \
-H 'X-Requested-With: XMLHttpRequest' \
-H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
-H 'Cookie: <some PHP session cookie>' \
--data-raw 'username=admin&password=<something that is not my password>' \
--compressed \
--insecure
```
Looking at the obfuscated JavaShit (`login.js`), we see something like follows:
```js
doPbkdf2NotCoded(doPbkdf2NotCoded("<password>", "<salt>"), "<saltwebui>")
```
quick check reveals: Yes, that returns the token used for the login. :heavy_check_mark:
Ok, so what does `doPbkdf2NotCoded` do?
```js
function doPbkdf2NotCoded(_0x365ad6, _0x470596) {
var _0x51b261 = sjcl[_0x5bfa('0x10')][_0x5bfa('0x11')](_0x365ad6, _0x470596, 0x3e8, 0x80);
var _0x279f24 = sjcl[_0x5bfa('0xc')][_0x5bfa('0x12')]['fromBits'](_0x51b261);
return _0x279f24;
}
```
easy, isn't it? %)
Turns out, `sjcl` is not yet another obfuscated JS function, but this [thingie](https://github.com/bitwiseshiftleft/sjcl).
Translated to something slightly more human readable (using the JS console)
```js
function whatTheFuck(param1, param2) {
// a, b, c, d
var temp = sjcl["misc"]["pbkdf2"](param1, param2, 0x3e8, 0x80)
return sjcl["codec"]["hex"]["fromBits"](temp)
}
```
From here, I started the GoLang implementation.

77
collector/api.go Normal file
View file

@ -0,0 +1,77 @@
package collector
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"golang.org/x/crypto/pbkdf2"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"strings"
"time"
)
type VodafoneStation struct {
URL string
Password string
client *http.Client
}
type LoginResponse struct {
Error string `json:"error"`
Salt string `json:"salt"`
SaltWebUI string `json:"saltwebui"`
}
func NewVodafoneStation(url, password string) *VodafoneStation {
cookieJar, err := cookiejar.New(nil)
if err != nil {
panic(err)
}
return &VodafoneStation{
URL: url,
Password: password,
client: &http.Client{
Jar: cookieJar,
Timeout: time.Second * 2,
},
}
}
func (v *VodafoneStation) getLoginSalts() (*LoginResponse, error) {
requestBody := strings.NewReader("username=admin&password=seeksalthash")
response, err := v.client.Post(v.URL+"/api/v1/session/login", "application/x-www-form-urlencoded", requestBody)
if err != nil {
return nil, err
}
if response.Body != nil {
defer response.Body.Close()
}
responseBody, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
loginResponse := &LoginResponse{}
err = json.Unmarshal(responseBody, loginResponse)
if err != nil {
return nil, err
}
if loginResponse.Error != "ok" {
return nil, fmt.Errorf("Got non error=ok message from vodafone station")
}
return loginResponse, nil
}
// GetLoginPassword derives the password using the given salts
func GetLoginPassword(password, salt, saltWebUI string) string {
return DoPbkdf2NotCoded(DoPbkdf2NotCoded(password, salt), saltWebUI)
}
// Equivalent to the JS doPbkdf2NotCoded (see README.md)
func DoPbkdf2NotCoded(key, salt string) string {
temp := pbkdf2.Key([]byte(key), []byte(salt), 0x3e8, 0x80, sha256.New)
fmt.Println(hex.EncodeToString(temp))
return hex.EncodeToString(temp[:16])
}

20
collector/api_test.go Normal file
View file

@ -0,0 +1,20 @@
package collector_test
import (
"github.com/fluepke/vodafone-station-exporter/collector"
"testing"
)
func TestDoPbkdf2NotCoded(t *testing.T) {
result := collector.DoPbkdf2NotCoded("EqAM2KtT", "2awfm2st3cej")
if result != "c2523cb6738663f9d9223c905c59cbb6" {
t.Errorf("Got %s", result)
}
}
func TestGetLoginPassword(t *testing.T) {
loginPassword := collector.GetLoginPassword("EqAM2KtT", "2awfm2st3cej", "4hbeVQ1Z6HK2")
if loginPassword != "b000b59875d1dc81bcd9d8f658fc7e77" {
t.Errorf("Derivation of login password failed!")
}
}