Debug School

rakesh kumar
rakesh kumar

Posted 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.

website-hacking-seo-spamincident-response

Top comments (0)