Skip to content

Commit 86c6780

Browse files
committed
Initial Release
0 parents  commit 86c6780

29 files changed

Lines changed: 3155 additions & 0 deletions

.github/workflows/ci.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Set up Python ${{ matrix.python-version }}
19+
uses: actions/setup-python@v5
20+
with:
21+
python-version: ${{ matrix.python-version }}
22+
cache: pip
23+
cache-dependency-path: pyproject.toml
24+
25+
- name: Install
26+
run: |
27+
python -m pip install --upgrade pip
28+
pip install -e ".[dev]"
29+
30+
- name: Run tests
31+
run: pytest -q

.github/workflows/publish.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*.*.*"
7+
workflow_dispatch:
8+
9+
jobs:
10+
build:
11+
name: Build distribution
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Set up Python
17+
uses: actions/setup-python@v5
18+
with:
19+
python-version: "3.12"
20+
21+
- name: Install build deps
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install build
25+
26+
- name: Verify tag matches pyproject version
27+
if: startsWith(github.ref, 'refs/tags/v')
28+
run: |
29+
tag="${GITHUB_REF#refs/tags/v}"
30+
ver=$(python -c "import tomllib,sys; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
31+
if [ "$tag" != "$ver" ]; then
32+
echo "::error::tag v$tag does not match pyproject version $ver"
33+
exit 1
34+
fi
35+
36+
- name: Build sdist + wheel
37+
run: python -m build
38+
39+
- name: Upload artifacts
40+
uses: actions/upload-artifact@v4
41+
with:
42+
name: dist
43+
path: dist/
44+
45+
publish:
46+
name: Publish to PyPI
47+
needs: build
48+
runs-on: ubuntu-latest
49+
environment:
50+
name: pypi
51+
url: https://pypi.org/p/firefetch
52+
steps:
53+
- name: Download artifacts
54+
uses: actions/download-artifact@v4
55+
with:
56+
name: dist
57+
path: dist/
58+
59+
- name: Publish to PyPI
60+
uses: pypa/gh-action-pypi-publish@release/v1
61+
with:
62+
password: ${{ secrets.PYPI_API_TOKEN }}

.gitignore

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
*.egg
7+
*.egg-info/
8+
.eggs/
9+
dist/
10+
build/
11+
.installed.cfg
12+
*.manifest
13+
*.spec
14+
15+
# Virtual envs
16+
.venv/
17+
venv/
18+
env/
19+
ENV/
20+
21+
# Tests / coverage / type checking
22+
.pytest_cache/
23+
.coverage
24+
.coverage.*
25+
htmlcov/
26+
.tox/
27+
.nox/
28+
coverage.xml
29+
*.cover
30+
.cache
31+
.mypy_cache/
32+
.ruff_cache/
33+
.pyre/
34+
.pytype/
35+
36+
# Editor / OS
37+
.idea/
38+
.vscode/
39+
*.swp
40+
*.swo
41+
.DS_Store
42+
Thumbs.db
43+
44+
# firefetch runtime artefacts
45+
.cache/
46+
*.apk
47+
*.xapk
48+
*.apks
49+
*.apkm
50+
firefetch-*.json
51+
/tmp/firefetch-apks/
52+
53+
# Secrets / local overrides
54+
.env
55+
.envrc
56+
*.local
57+
58+
# Build / packaging metadata
59+
pip-wheel-metadata/
60+
.python-version

.pre-commit-config.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v5.0.0
4+
hooks:
5+
- id: trailing-whitespace
6+
- id: end-of-file-fixer
7+
- id: check-yaml
8+
- id: check-toml
9+
- id: check-merge-conflict
10+
- id: check-added-large-files
11+
args: ["--maxkb=5120"]
12+
13+
- repo: https://github.com/pycqa/isort
14+
rev: 5.13.2
15+
hooks:
16+
- id: isort
17+
args: ["--profile", "black"]
18+
19+
- repo: https://github.com/psf/black
20+
rev: 24.10.0
21+
hooks:
22+
- id: black

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# FireFetch
2+
3+
A Firebase audit tool, mostly aimed at mobile apps.
4+
5+
Point it at an APK, an Android package name, or a set of Firebase values you
6+
already have. It checks Remote Config, Realtime Database, Firestore, Cloud
7+
Storage, Auth, and Hosting and tells you what's
8+
exposed.
9+
10+
## Install
11+
12+
```bash
13+
pipx install firefetch
14+
```
15+
16+
17+
## Use it
18+
19+
```bash
20+
# you already have the apk
21+
firefetch apk app-release.apk
22+
23+
# you only know the package name
24+
firefetch apk com.example.app
25+
26+
# no apk; just creds you already have
27+
firefetch manual --project-id foo --api-key AIzaSy... --app-id 1:1234:android:abc
28+
```
29+
30+
Handy flags: `--json out.json` for a structured dump, `--no-write` to skip
31+
write probes (on by default; they write a tiny payload at a unique path and
32+
delete it). `firefetch apk --help` for the rest.
33+
34+
## What you get
35+
36+
![](assets/terminal.png)
37+
38+
## Dev
39+
40+
```bash
41+
git clone https://github.com/bitthebyte/firefetch
42+
cd firefetch
43+
python -m venv .venv && source .venv/bin/activate
44+
pip install -e ".[dev]"
45+
pytest
46+
```

assets/terminal.png

4.26 MB
Loading

pyproject.toml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
[build-system]
2+
requires = ["setuptools>=68", "wheel"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "firefetch"
7+
version = "0.2.0"
8+
description = "Extract Firebase credentials from Android APKs and probe their backends."
9+
readme = "README.md"
10+
requires-python = ">=3.9"
11+
license = { text = "MIT" }
12+
authors = [{ name = "bitthebyte" }]
13+
keywords = ["android", "apk", "firebase", "security", "recon", "pentest"]
14+
classifiers = [
15+
"Development Status :: 3 - Alpha",
16+
"Environment :: Console",
17+
"Intended Audience :: Information Technology",
18+
"Programming Language :: Python :: 3",
19+
"Programming Language :: Python :: 3 :: Only",
20+
"Topic :: Security",
21+
]
22+
dependencies = [
23+
"requests>=2.31",
24+
"curl_cffi>=0.7",
25+
"rich>=13.0",
26+
]
27+
28+
[project.optional-dependencies]
29+
dev = [
30+
"pytest>=7.4",
31+
"requests-mock>=1.11",
32+
"black>=24.0",
33+
"isort>=5.13",
34+
"pre-commit>=3.5",
35+
]
36+
37+
[project.scripts]
38+
firefetch = "firefetch.cli:main"
39+
40+
[project.urls]
41+
Homepage = "https://github.com/bitthebyte/firefetch"
42+
43+
[tool.setuptools.packages.find]
44+
where = ["src"]
45+
46+
[tool.setuptools.package-data]
47+
firefetch = ["py.typed"]
48+
49+
[tool.black]
50+
line-length = 88
51+
target-version = ["py39", "py310", "py311", "py312"]
52+
53+
[tool.isort]
54+
profile = "black"
55+
line_length = 88
56+
src_paths = ["src", "tests"]

src/firefetch/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "0.2.0"

src/firefetch/__main__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from firefetch.cli import main
2+
3+
if __name__ == "__main__":
4+
raise SystemExit(main())

src/firefetch/_http.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from __future__ import annotations
2+
3+
import os
4+
5+
_PROXY: str | None = None
6+
_VERIFY: bool = True
7+
8+
9+
def configure(proxy: str | None, insecure: bool) -> None:
10+
global _PROXY, _VERIFY
11+
_PROXY = proxy
12+
_VERIFY = not insecure
13+
14+
if proxy:
15+
os.environ["HTTP_PROXY"] = proxy
16+
os.environ["HTTPS_PROXY"] = proxy
17+
18+
if insecure:
19+
import urllib3
20+
21+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
22+
23+
import requests
24+
25+
original = requests.Session.request
26+
27+
def patched(self, method, url, **kwargs):
28+
kwargs.setdefault("verify", False)
29+
return original(self, method, url, **kwargs)
30+
31+
requests.Session.request = patched # type: ignore[assignment]
32+
33+
34+
def proxy() -> str | None:
35+
return _PROXY
36+
37+
38+
def verify() -> bool:
39+
return _VERIFY

0 commit comments

Comments
 (0)