Add README.md
This commit is contained in:
commit
e2904e8cb4
3 changed files with 168 additions and 0 deletions
71
README.md
Normal file
71
README.md
Normal 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
77
collector/api.go
Normal 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
20
collector/api_test.go
Normal 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!")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue