Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
uses: docker/setup-buildx-action@v3

- name: Build Docker image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
load: true
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.8'
python-version: '3.12'

- name: Install flake8
run: |
Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Tests

on:
push:
branches: [ "**" ]
pull_request:
branches: [ "**" ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: python -m pip install .
- run: python -m unittest discover -s tests -v
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
__pycache__/
*.py[cod]
.pytest_cache/
.venv/
dist/
build/
*.egg-info/
18 changes: 4 additions & 14 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
# Use Python 3.8 as base - this version has good compatibility with older packages
FROM python:3.8-slim
FROM python:3.12-slim

# Set working directory
WORKDIR /app

# Install git (needed for pip install from git repos)
RUN apt-get update && \
apt-get install -y git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Copy only the necessary files
# Copy only the files needed to install and run the project
COPY github-dork.py /app/
COPY github-dorks.txt /app/
COPY setup.py /app/
COPY README.md /app/
COPY requirements.txt /app/

# Install dependencies
# Using the specific version of github3.py that's known to work
RUN pip install --no-cache-dir github3.py==1.0.0a2 feedparser==6.0.2
RUN pip install --no-cache-dir .

# Set environment variables
ENV PYTHONUNBUFFERED=1
Expand All @@ -28,4 +18,4 @@ ENV PYTHONIOENCODING=UTF-8
# Create volume for potential output files
VOLUME ["/app/output"]

ENTRYPOINT ["python", "github-dork.py"]
ENTRYPOINT ["python", "github-dork.py"]
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[![Docker Build & Test](https://github.com/techgaun/github-dorks/actions/workflows/docker-build.yml/badge.svg)](https://github.com/techgaun/github-dorks/actions/workflows/docker-build.yml)

# Github Dorks
# GitHub Dorks

[Github Search](https://github.com/search) is a quite powerful and useful feature that can be used to search for sensitive data on repositories. Collection of Github dorks can reveal sensitive personal and/or organizational information such as private keys, credentials, authentication tokens, etc. This list is supposed to be useful for assessing security and performing pen-testing of systems.

Expand Down Expand Up @@ -61,10 +61,20 @@ GH_TOKEN=<github_token> github-dork.py -u dev-nepal # search using
GH_URL=https://github.example.com github-dork.py -u dev-nepal # search a GitHub Enterprise instance
```

### Development

Run the dependency-free unit test suite with:

```shell
python -m unittest discover -s tests -v
```

The CI test matrix covers Python 3.10 through 3.13.

### Limitations

- Authenticated requests get a higher rate limit. But, since this tool waits for the api rate limit to be reset (which is usually less than a minute), it can be slightly slow.
- Output formatting is not great. PR welcome
- Search results can be printed to the terminal or written as CSV.
- ~~Handle rate limit and retry. PR welcome~~

### Contribution
Expand Down
33 changes: 23 additions & 10 deletions github-dork.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
import github3 as github
import os
import argparse
import csv
import time
import feedparser
from copy import copy
from contextlib import nullcontext
from sys import stderr, prefix

__version__ = '0.1.1'

gh_user = os.getenv('GH_USER', None)
gh_pass = os.getenv('GH_PWD', None)
gh_token = os.getenv('GH_TOKEN', None)
Expand Down Expand Up @@ -93,22 +97,28 @@ def search(repo_to_search=None,
gh_dorks_file = filename
break

if not os.path.isfile(gh_dorks_file):
if gh_dorks_file is None or not os.path.isfile(gh_dorks_file):
raise Exception('Error, the dorks file path is not valid')
if user_to_search:
print("Scanning User: ", user_to_search)
if repo_to_search:
print("Scanning Repo: ", repo_to_search)
found = False

outputFile = None
if output_filename:
outputFile = open(output_filename, 'w')
output_context = (
open(output_filename, 'w', newline='', encoding='utf-8')
if output_filename else nullcontext(None)
)

with open(gh_dorks_file, 'r') as dork_file:
with open(gh_dorks_file, 'r', encoding='utf-8') as dork_file, output_context as output_file:
# Write CSV Header
if outputFile:
outputFile.write('Issue Type (Dork), Text Matches, File Path, Score/Relevance, URL of File\n')
csv_writer = None
if output_file:
csv_writer = csv.writer(output_file)
csv_writer.writerow([
'Issue Type (Dork)', 'Text Matches', 'File Path',
'Score/Relevance', 'URL of File'
])
for dork in dork_file:
dork = dork.strip()
if not dork or dork[0] in '#;':
Expand All @@ -133,8 +143,11 @@ def search(repo_to_search=None,
}

# Either write to file or print output
if outputFile:
outputFile.write('{dork}, {text_matches}, {path}, {score}, {url}\n'.format(**fmt_args))
if csv_writer:
csv_writer.writerow([
fmt_args['dork'], fmt_args['text_matches'],
fmt_args['path'], fmt_args['score'], fmt_args['url']
])
else:
result = '\n'.join([
'Found result for {dork}',
Expand All @@ -161,7 +174,7 @@ def main():
epilog='Use responsibly, Enjoy pentesting')

parser.add_argument(
'-v', '--version', action='version', version='%(prog)s 0.1.1')
'-v', '--version', action='version', version='%(prog)s ' + __version__)

group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github3.py==1.0.0a2
feedparser==6.0.2
github3.py==4.0.1
feedparser>=6.0.12,<7
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@

setup(
name='github-dorks',
version='0.1',
version='0.1.1',
description='Find leaked secrets via github search.',
license='Apache License 2.0',
long_description=long_description,
author='Samar Dhwoj Acharya (@techgaun)',
long_description_content_type='text/markdown',
scripts=['github-dork.py'],
data_files=[('github-dorks', ['github-dorks.txt'])],
python_requires='>=3.10',
install_requires=[
'github3.py==4.0.1',
'feedparser==6.0.2',
'feedparser>=6.0.12,<7',
],
)
94 changes: 94 additions & 0 deletions tests/test_github_dork.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import csv
import importlib.util
import io
import sys
import tempfile
import types
import unittest
from contextlib import redirect_stdout
from pathlib import Path
from unittest.mock import patch


class FakeGitHubError(Exception):
pass


class FakeForbiddenError(FakeGitHubError):
pass


fake_github3 = types.ModuleType('github3')
fake_github3.GitHub = lambda **kwargs: None
fake_github3.GitHubEnterprise = lambda **kwargs: None
fake_github3.exceptions = types.SimpleNamespace(
ForbiddenError=FakeForbiddenError,
GitHubError=FakeGitHubError,
)
sys.modules.setdefault('github3', fake_github3)
sys.modules.setdefault('feedparser', types.ModuleType('feedparser'))

spec = importlib.util.spec_from_file_location(
'github_dork', Path(__file__).parents[1] / 'github-dork.py'
)
github_dork = importlib.util.module_from_spec(spec)
spec.loader.exec_module(github_dork)


class SearchResult:
text_matches = ['secret, with comma']
path = 'config/file.env'
score = 42
html_url = 'https://github.example/result'


class GitHubClient:
def search_code(self, query):
self.query = query
return iter([SearchResult()])


class SearchTests(unittest.TestCase):
def test_writes_valid_csv_and_scopes_query_to_repository(self):
client = GitHubClient()
with tempfile.TemporaryDirectory() as directory:
dorks = Path(directory) / 'dorks.txt'
output = Path(directory) / 'results.csv'
dorks.write_text('# comment\nfilename:.env PASSWORD\n', encoding='utf-8')

with patch.object(github_dork, 'gh', client):
github_dork.search(
repo_to_search='owner/repo',
gh_dorks_file=str(dorks),
output_filename=str(output),
)

with output.open(newline='', encoding='utf-8') as output_file:
rows = list(csv.reader(output_file))

self.assertEqual(client.query, 'filename:.env PASSWORD repo:owner/repo')
self.assertEqual(len(rows), 2)
self.assertEqual(rows[1][0], client.query)
self.assertEqual(rows[1][1], "['secret, with comma']")

def test_reports_when_no_results_are_found(self):
client = GitHubClient()
client.search_code = lambda query: iter([])
with tempfile.TemporaryDirectory() as directory:
dorks = Path(directory) / 'dorks.txt'
dorks.write_text('filename:.env\n', encoding='utf-8')
stdout = io.StringIO()
with patch.object(github_dork, 'gh', client), redirect_stdout(stdout):
github_dork.search(
user_to_search='example', gh_dorks_file=str(dorks)
)

self.assertIn('No results for your dork search user:example', stdout.getvalue())

def test_rejects_missing_dorks_file_with_clear_error(self):
with self.assertRaisesRegex(Exception, 'dorks file path is not valid'):
github_dork.search(gh_dorks_file='/does/not/exist')


if __name__ == '__main__':
unittest.main()
Loading