Skip to content

get_internet_config() reports placeholder ppp/ppp instead of the real PPPoE credentials (FRITZ!Box 7590, FRITZ!OS 8.25) #2

Description

@rimuln

Summary

On a FRITZ!Box 7590 running FRITZ!OS 154.08.25, get_internet_config() never finds the
ISP credentials — and instead fills serial_username / serial_password with the literal
placeholder ppp / ppp taken from an unrelated block. The output therefore looks like it
contains a credential pair, so there is no signal that the real one was missed. A silently
wrong value is considerably worse than a missing field, which is why I'm reporting it rather
than just patching locally.

Environment

  • FRITZ!Box 7590, FRITZ!OS 154.08.25 (export labelled 154.08.25i)
  • VDSL with a manually configured provider (active_provider = "other")
  • Verified against a real password-protected export: 149 encrypted values decrypted with
    0 MD5 integrity failures, so decryption itself works perfectly. This is purely a
    key-lookup problem.

What happens

get_internet_config() looks for the ISP credentials under three key names:

pppoe_user = self._get_cfg_encrypted(ar7, "pppoeuser") or self._get_cfg_value(ar7, "pppoeuser")
pppoe_pass = self._get_cfg_encrypted(ar7, "pppoepwd") or self._get_cfg_encrypted(ar7, "ppppasswd")

None of pppoeuser, pppoepwd or ppppasswd exists anywhere in this export, so neither
pppoe_username nor pppoe_password is ever added to the result.

Execution then falls through to the serial lookup:

serial_user = self._get_cfg_encrypted(ar7, "username") or self._get_cfg_value(ar7, "username")
serial_pass = self._get_cfg_encrypted(ar7, "passwd")

Because _get_cfg_encrypted() runs re.search() across the entire ar7.cfg text with no
block scoping, this returns the first username / passwd in the file. Here that is
ar7cfg > serialcfg, which decrypts to the literal string ppp for both:

"internet": {
  "mode": "dsldmode_router",
  "ipv4_mode": "ipv4_normal",
  "serial_username": "ppp",
  "serial_password": "ppp",
  "wan_interfaces": [ ... ]
}

Someone recovering a forgotten ISP password will reasonably read that as the answer.

Where the credentials actually live

ar7cfg > targets > local
    username = "$$$$...";   <- ISP login
    passwd   = "$$$$...";   <- ISP password

Both are $$$$-encrypted and decrypt correctly with the existing decrypt_value().

Why this slipped through testing

The committed sample output FRITZBox_decoded_anonymized.json shows exactly the same
symptom:

"serial_username": "ppp",
"serial_password": "ppp",

with no pppoe_* keys present. That sample also contains
"provider": "tr069_unitymedia_hessen" and WAN interface nsgmii1 — i.e. a cable box
provisioned over TR-069, which has no PPPoE credentials to find in the first place. So the
PPPoE branch was never exercised by the available fixture, and the ppp/ppp output looked
like a plausible result rather than a red flag.

Suggested fix

Scope the lookup to the enclosing block instead of searching the whole file:

  1. Extract the targets block, then each named sub-block inside it (local, …).
  2. Read username / passwd from that sub-block and report them as
    pppoe_username / pppoe_password (or a provider-neutral isp_*).
  3. Keep the existing pppoeuser / pppoepwd / ppppasswd names as an additional lookup,
    for firmware that does use them.
  4. Either drop the serial_* fallback or reject obvious placeholders — a three-character
    value of ppp is never a real credential. Emitting nothing is much safer than emitting
    something wrong.

More broadly: _get_cfg_value() / _get_cfg_encrypted() taking the first global match is
fragile for any key that repeats. This one export has 31 username and 12 passwd
keys spread across serialcfg, targets > local, ddns > accounts, webui, TR_064,
emailnotify and 20 entries under apps > apps — so a global first-match is effectively a
lottery, and which value wins depends on file ordering.

I worked around it locally with a small brace-depth walker that yields
(line_number, line, nesting_path) for each section and dumps every $$$$ value annotated
with its block path; that surfaces the credentials unambiguously and also makes it obvious
which of the 31 username keys is which. Happy to open a PR with a block-scoped
get_internet_config() if that would be useful.

Aside

Genuinely nice tool to audit before trusting it with a file full of secrets: single file, one
dependency, no network access of any kind, and the key-derivation chain
(MD5(password) + 16 zero bytes → AES-256-CBC on the header Password= field → first 16
bytes + 16 zero bytes as the session key) matches the community's reverse-engineering of the
AVM scheme exactly. That auditability is the right property for this kind of utility, and it
is why I was comfortable running it at all.


Unrelated nit: README.md links to a LICENSE file for the MIT terms, but no LICENSE
file is present in the repository.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions