Debug School

rakesh kumar
rakesh kumar

Posted on • Edited on

How to Recover a Hacked Website with Git: Inspect Changes and Restore Clean Files

Git keeps a history of the files you have committed. If someone changes a live website file such as index.php, you can use Git to compare the live file with a known version, inspect older versions, and restore a clean copy. Commands such as git status show which files changed, while git diff shows the exact changes. After verifying that a committed version is clean, git checkout or git restore can put that version back.
In my Laravel website incident, the homepage showed a gambling page because the live index.php had been changed. The version stored in Git was clean, so restoring index.php helped bring back the normal homepage. Git helped recover the file; it did not explain how the change happened or secure the server. Those require a separate investigation and security fixes.

Keeps file history: Git stores versions of files that were committed. You can inspect an earlier index.php if that version exists in the repository.

Shows which tracked files changed: git status --short helps you spot modified or deleted files. It also lists untracked files, such as an unexpected blog/index.html.

Shows the exact changes: git diff -- index.php compares the working file with Git’s staged version. This can reveal injected content. Use git diff --cached -- index.php to check changes that were already staged.

Helps you find a known good version: git log --oneline --follow -- index.php lists earlier commits affecting the file. git show :index.php lets you read an older version without changing the live website.

Restores one file: After verifying the Git version is clean, git checkout -- index.php can replace the changed working file. You can also use git restore to select a particular older commit.

Can reverse a bad commit: If the unwanted change was committed, git revert creates another commit that reverses it while keeping the history.

Can help after an accidental reset: git reflog shows where the local branch pointed recently, which may help you find a lost commit. It cannot recover every overwritten, uncommitted change.

What Happened on My Laravel Website

  • Visitors saw a gambling page because the live index.php had been changed.
  • The version of index.php stored in Git was clean.
  • Restoring the file with git checkout -- index.php .htaccess helped bring back the normal homepage.
  • Git restored those tracked files, but any separate untracked suspicious file needed its own investigation.
  • Restoring the homepage did not reveal how the files were changed or secure the server. I still needed to investigate the entry point, check other files, and address the reported public access to .env. Before replacing a suspicious file, save a copy outside the web directory. Its contents and timestamps may be useful during the investigation.

How to Restore a Changed File with git checkout

How to Restore a Changed File with git checkout
website-hacking-seo-spamincident-response

Use of Different Git command

git status --short — see which files changed

git status --short
Enter fullscreen mode Exit fullscreen mode

Before: index.php and .htaccess were changed; an extra file appeared.

 M index.php
 M .htaccess
?? blog/index.html
Enter fullscreen mode Exit fullscreen mode

M means modified. ?? means untracked: Git has no committed copy of that file.
After: This command changes nothing. Running it again gives the same output.

  1. git diff -- index.php .htaccess — see what changed
git diff -- index.php .htaccess
Enter fullscreen mode Exit fullscreen mode

Before: Git’s index.php starts Laravel, but the working file contains spam.
Example output:

diff --git a/index.php b/index.php
--- a/index.php
+++ b/index.php
@@
-<?php require __DIR__.'/vendor/autoload.php';
+<html><title>CERUTU4D</title>...</html>
Enter fullscreen mode Exit fullscreen mode

After: No files change. The diff still appears if you run it again.
This shows unstaged changes. If git status --short shows M with the M in the first column, inspect staged changes too:

git diff --cached -- index.php .htaccess
Enter fullscreen mode Exit fullscreen mode

3.** git log --oneline --follow -- index.php — find earlier commits**

git log --oneline --follow -- index.php
Enter fullscreen mode Exit fullscreen mode

Example output:

a81c390 Update Laravel entry point
74b2e10 Fix bootstrap path
31d9f42 Initial homepage
Enter fullscreen mode Exit fullscreen mode

After: Nothing changes. You now have commit hashes to inspect. --follow also helps trace the file across a rename.

  1. git show :index.php — read an old version
git show 74b2e10:index.php
Enter fullscreen mode Exit fullscreen mode

Example output:

<?php

require __DIR__.'/vendor/autoload.php';

$app = require_once __DIR__.'/bootstrap/app.php';
Enter fullscreen mode Exit fullscreen mode

Before and after: Your live index.php stays exactly as it was. This is a good way to check whether commit 74b2e10 has the version you actually want.

  1. git checkout -- index.php .htaccess — restore the current staged versions
git checkout -- index.php .htaccess
Enter fullscreen mode Exit fullscreen mode

Before:

Working index.php: gambling page
Git index.php:     clean Laravel entry point
Enter fullscreen mode Exit fullscreen mode

Command output: Usually none.
After:

Working index.php: clean Laravel entry point
Git index.php:     clean Laravel entry point
Enter fullscreen mode Exit fullscreen mode

Check the result:
git status --short

?? blog/index.html
Enter fullscreen mode Exit fullscreen mode

The modifications to index.php and .htaccess are gone. The untracked blog/index.html is still there. This is why your homepage could recover while a separate suspicious file remained.
Technically, git checkout -- file copies from Git’s index (staging area). If someone staged a malicious version first, this command could copy that version. Inspect staged changes when that is possible.

  1. git restore --source= --worktree -- index.php — recover an older file without staging it Suppose the current committed homepage is broken, and you verified that 74b2e10 has the clean version:
git restore --source=74b2e10 --worktree -- index.php
Enter fullscreen mode Exit fullscreen mode

Before:

HEAD index.php:    broken newer version
Working index.php: broken newer version
Enter fullscreen mode Exit fullscreen mode

Command output: Usually none.
After:

HEAD index.php:    broken newer version
Working index.php: clean version from 74b2e10
Enter fullscreen mode Exit fullscreen mode
git status --short
Enter fullscreen mode Exit fullscreen mode
 M index.php
Enter fullscreen mode Exit fullscreen mode

The leading space before M means the recovered file is changed in your working tree but not staged. You can review it with git diff -- index.php.

  1. git checkout -- index.php — recover an older file and stage it
git checkout 74b2e10 -- index.php
Enter fullscreen mode Exit fullscreen mode

Before: The working file and staged version contain the newer version.
Command output: Usually none.
After:

git status --short
Enter fullscreen mode Exit fullscreen mode

M index.php

Here M is in the first column: the older file is already staged. Check what would go into a commit with:

git diff --cached -- index.php
Enter fullscreen mode Exit fullscreen mode

That staging behavior is the key difference from the git restore --worktree example above.

  1. git revert — reverse a bad committed change Suppose commit c52f901 introduced an unwanted homepage change: git revert c52f901

Before:

c52f901 Add unwanted homepage change
a81c390 Previous good commit
Enter fullscreen mode Exit fullscreen mode

Example output:

[main e63a104] Revert "Add unwanted homepage change"
 1 file changed, 1 insertion(+), 1 deletion(-)
Enter fullscreen mode Exit fullscreen mode

After:

e63a104 Revert "Add unwanted homepage change"
c52f901 Add unwanted homepage change
a81c390 Previous good commit
Enter fullscreen mode Exit fullscreen mode

Git keeps the bad commit in history and adds a new commit that reverses it. A revert can require conflict resolution if later commits changed the same lines.

  1. git reflog — find where your branch pointed before a reset Suppose someone ran git reset --hard and you need to find the previous commit: git reflog

Example output:

74b2e10 HEAD@{0}: reset: moving to 74b2e10
a81c390 HEAD@{1}: commit: Update Laravel entry point
Enter fullscreen mode Exit fullscreen mode

Before and after: git reflog changes nothing. It tells you that this local repository previously pointed at a81c390. You can inspect it first:

git show --stat a81c390
Enter fullscreen mode Exit fullscreen mode

A reflog may help recover committed work after a reset. It cannot bring back an uncommitted file overwritten by reset --hard, and its entries do not last forever.
For your actual incident: start with status, both diff commands, log, and show; preserve suspicious files; then restore only the verified files you need. git reset --hard affects the whole tracked working tree, so it is a poor first response to one changed homepage file.

Compare a File’s Historical Versions Using Commit IDs

Step 1: Find commits that changed the file

git log --oneline --follow -- resources/views/home.blade.php
Enter fullscreen mode Exit fullscreen mode

Example output:
e4f5a6b Update homepage banner
a1b2c3d Add homepage

Suppose a1b2c3d is the older version and e4f5a6b is the newer version.
Step 2: See the content difference

git diff a1b2c3d e4f5a6b -- resources/views/home.blade.php
Enter fullscreen mode Exit fullscreen mode

Example output:

-<h1>Explore HolidayLandmark</h1>
+<h1>Find Your Next Trip</h1>
Enter fullscreen mode Exit fullscreen mode

The - line is from the older version. The + line is from the newer version.
Step 3: Read either complete version

git show a1b2c3d:resources/views/home.blade.php
git show e4f5a6b:resources/views/home.blade.php
Enter fullscreen mode Exit fullscreen mode

Example output from the first command:

Explore HolidayLandmark

Example output from the second command:

Find Your Next Trip

Before and after: The live resources/views/home.blade.php is untouched.
git log --follow helps find history across a rename, but git show needs the path that existed in the chosen commit. If the file had a different name then, use its old path for the old commit.

Another Example

Using 28 July 2026 and 21 September 2026 as examples (two months and one week before 28 September)

  1. Find the last commit available on each date Run these from the correct Git repository:
git log -1 --before="2026-07-29 00:00:00" --format="%h %ci %s" HEAD
git log -1 --before="2026-09-22 00:00:00" --format="%h %ci %s" HEAD
Enter fullscreen mode Exit fullscreen mode

Example output:

a1b2c3d 2026-07-27 16:20:00 +0530 Update homepage
e4f5a6b 2026-09-21 18:45:00 +0530 Update booking page
Enter fullscreen mode Exit fullscreen mode

Here, a1b2c3d is the July snapshot and e4f5a6b is the September snapshot. The first commit may be dated July 27 because there was no commit on July 28.

  1. See which files differ between those snapshots
git diff --name-status a1b2c3d e4f5a6b
Enter fullscreen mode Exit fullscreen mode

Example output:

M       index.php
M       .htaccess
A       resources/views/trips/show.blade.php
D       old-home.html
Enter fullscreen mode Exit fullscreen mode

M = modified, A = added, D = deleted. This answers: “Which committed files were different one week ago compared with two months ago?”

  1. See the actual content differences For all those files:
git diff a1b2c3d e4f5a6b
Enter fullscreen mode Exit fullscreen mode

For only index.php:

git diff a1b2c3d e4f5a6b -- index.php
Enter fullscreen mode Exit fullscreen mode

Example output:

-<?php require __DIR__.'/vendor/autoload.php';
+<?php require __DIR__.'/new-bootstrap.php';
Enter fullscreen mode Exit fullscreen mode

A line beginning with - existed in the July version; a line beginning with + existed in the September version. These commands only display differences; they do not change your files.

  1. If you mean “what was changed during each period” That is a different question from comparing two snapshots. List commits and their files for each date range:
git log --since="2026-07-28 00:00:00" --until="2026-07-29 00:00:00" --name-status --oneline
git log --since="2026-09-21 00:00:00" --until="2026-09-22 00:00:00" --name-status --oneline
Enter fullscreen mode Exit fullscreen mode

These show changes committed during July 28 and September 21, respectively. If no commits occurred on a date, the output will be empty.
Important for your hack investigation: Git’s historical commands show committed history. If someone directly edited the live index.php without committing it, that edit will not appear in a July-to-September commit comparison. Compare the current live working file against the selected commit with:

git diff e4f5a6b -- index.php
Enter fullscreen mode Exit fullscreen mode

Another EXAMPLE

ou can give Git the dates and let it find the commits automatically. To compare index.php as it stood about two months ago (28 July 2026) with one week ago (21 September 2026), run this inside the repository:

OLD=$(git rev-list -1 --before="2026-07-29 00:00:00 +0530" HEAD)
NEW=$(git rev-list -1 --before="2026-09-22 00:00:00 +0530" HEAD)

git diff "$OLD" "$NEW" -- index.php

Enter fullscreen mode Exit fullscreen mode

The dates use the start of the following day, so Git picks the latest commit available by the end of July 28 and September 21. You do not need to look up or type a commit ID yourself.
Example output:

-<?php require __DIR__.'/vendor/autoload.php';
+<?php require __DIR__.'/bootstrap/new-autoload.php';
Enter fullscreen mode Exit fullscreen mode
  • is the line in the two-month-old version; + is the line in the one-week-old version. To see each complete file instead of just the difference:
git show "$OLD:index.php"
git show "$NEW:index.php"

Enter fullscreen mode Exit fullscreen mode

These commands only read Git history; they do not change your live file. They can show a version only if it was committed. If the attacker changed the live index.php without making a commit, compare that live file separately with git diff HEAD -- index.php.

Compare a File’s Historical Versions by Date Without Knowing Commit IDs

Step 1: Let Git find a commit for each date
Run these lines in the same terminal, from the repository directory:

OLD=$(git rev-list -1 --before="2026-07-29 00:00:00 +0530" HEAD)
NEW=$(git rev-list -1 --before="2026-09-22 00:00:00 +0530" HEAD)
Enter fullscreen mode Exit fullscreen mode

The commands normally print nothing. OLD now holds the latest reachable commit available by the end of 28 July; NEW does the same for 21 September.
Check that both were found:

echo "$OLD"
echo "$NEW"
Enter fullscreen mode Exit fullscreen mode

Example output:

a1b2c3d9...
e4f5a6b8...
Enter fullscreen mode Exit fullscreen mode

If either line is empty, stop: Git did not find a commit before that date on the current history.
Step 2: Compare the Blade file

git diff "$OLD" "$NEW" -- resources/views/home.blade.php
Enter fullscreen mode Exit fullscreen mode

Example output:

-<h1>Explore HolidayLandmark</h1>
+<h1>Find Your Next Trip</h1>
Enter fullscreen mode Exit fullscreen mode

Step 3: Read the complete file at each date

git show "$OLD:resources/views/home.blade.php"
git show "$NEW:resources/views/home.blade.php"
Enter fullscreen mode Exit fullscreen mode

Before and after: The live Blade file is untouched. Git displayed versions from committed history. If an attacker edited the live file without committing, compare that current file separately:

git diff HEAD -- resources/views/home.blade.php
Enter fullscreen mode Exit fullscreen mode

Another Example to compare a File’s Historical Versions by Date Without Knowing Commit IDs


OLD=$(git rev-list -1 --before="2026-07-29 00:00:00 +0530" HEAD)
NEW=$(git rev-list -1 --before="2026-09-22 00:00:00 +0530" HEAD)
git diff "$OLD" "$NEW" -- index.php
Enter fullscreen mode Exit fullscreen mode

Run them in the same terminal session, from the Git repository directory. OLD and NEW temporarily hold the commit IDs Git found. The first two lines normally print nothing; the third shows the content difference.
To check what Git found before comparing:

echo "$OLD"
echo "$NEW"
Enter fullscreen mode Exit fullscreen mode

If either prints an empty line, Gi

Another Example to compare a File’s Historical Versions by Date Without Knowing Commit IDs

OLD=$(git rev-list -1 --before="2026-07-29 00:00:00 +0530" HEAD)
NEW=$(git rev-list -1 --before="2026-09-22 00:00:00 +0530" HEAD)

git diff "$OLD" "$NEW" -- resources/views/home.blade.php
Enter fullscreen mode Exit fullscreen mode

That shows what changed in the Blade file between the two dates. To read the complete version from each date:

git show "$OLD:resources/views/home.blade.php"
git show "$NEW:resources/views/home.blade.php"
Enter fullscreen mode Exit fullscreen mode

For example, if your actual path is resources/views/trips/show.blade.php, substitute that full relative path in all three commands. If Git reports that the path does not exist in the older commit, the file may have been created or renamed after that date

website-hacking-seo-spamincident-response
root-domain-serving-the-blog-instead-of-your-laravel

prmptsia-hacking
chatgpt

Top comments (0)