Arroiz
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    --- title: NoSQLi --- <div class="header__logo" >NoSQL Injection: вроде скули, а вроде и нет! </div> <p> </p> <style> .header__logo { font-size: 30px; font-weight: 700; color: #270469; text-align: center; } </style> <style> .secret{ display: flex; margin-left: auto; margin-right: auto; width: 50%; } </style> <style> .secret__block{ background-color: #fff; display: block; max-width: 100%; height: auto; transition: opacity .8s linear; color: black; text-transform: uppercase; font-weight: 700; text-align: center; } </style> <style> .secret__block img{ opacity: 5; } </style> <style> .secret__block:hover { opacity: 2 ; } </style> <style> .secret__img { opacity: ; transition: transform 1.8s linear; } </style> <style> .secret__img img{ opacity: 0 ; transition: transform 1.8s linear; } </style> <style> .secret__img img: hover{ opacity: 5 ; transition: transform 1.8s linear; } </style> <style> .secret__img img { display: block; max-width: 100%; height: auto; transition: opacity 1.8s linear; } </style> <style> .secret__img:hover { opacity: 5 ; } </style> <style> .secret__inner { position: relative; background-color: #fff; } </style> <style> .secret__inner:hover .secret__img { transform: translate3d(-10px, -10px, 0); } </style> <style> .secret__inner img:hover { opacity: 0; transition: opacity 2s linear; } </style> <style> .secret__inner img { transition: opacity 2s linear; } </style> <style> .secret__inner:hover .secret__img img { opacity: 1; } </style> ![](https://i.imgur.com/nc4RnC2.png) <div class="TOC" > Содержание: <p> </p> </div> <style> .TOC { font-size: 26px; font-weight: 550; color: #270469; text-align: left; </style> > [TOC] --- # Вводная часть + примечание Всем привет! Сегодня мы будем рассматривать NoSQL-инъекции. Это одна из самых непростых для нахождения материалов тема + не так уж и много лаб есть в интернете(которые бы ещё работали и были бесплатными:D). Да-да, портсвиггера сегодня не будет - расходимся. А если серьёзно, то для практики по этой теме я буду использовать 2 лабы с [rootme](https://www.root-me.org/?lang=fr), и одну с [хабр статьи](https://habr.com/ru/companies/xakep/articles/143909/). Третью лабораторную в отличие от рутми лаб нужно самому собирать и запускать тоже у себя на хосте. И всё бы ничего, там же есть инструкция внутри, но есть 1 маленькое НО. Данная стаья 2012 года, и лаба тоже примерно такого возраста, поэтому если сейчас её запустить, то вылезут ошибки. На данный момент мне удалось её "починить" примерно на 50%: само веб-приложение работает, а вот лабы только половина(2 штуки работают как надо, ещё 2 некорректно работают(об этом позже), а последняя впринципе не работает). Если кому-то захочется самому попрактиковаться после прочтения данного топика, то [ВОТ](https://t.me/mtuci_ctf/8700) ссылка на эту же лабу, только я слегка её починил и сделал инструкцию более понятной. --- # Что такое NoSQL-Injection :::warning **NoSQL-инъекция (NoSQL Injection)** — это уязвимость в веб-приложении, которая позволяет злоумышленнику влиять на запросы, которые приложение делает к своей базе данных(бд). Благодаря данной уязвимости злоумышленник может получить доступ к различным конфиденциальным данным в бд: пароли, заметки и пр. Помимо этого злоумышленник может удалять и изменять данные, а также выполнять свои скрипты на сервере. И много чего ещё другого! ::: Звучит знакомо, не правда ли?) И вы верно подумали, так как это по сути слегка изменённое определение SQL-инъекций в одном из моих топиков. Всё дело в том, что SQl-инъекции и NoSQL-инъекции концептуально похожи: злоумышленник влияет на запросы к БД и происходит какая-то гадость(получение, изменение или удаление данных, обход аутентификации, выполнение кода и тд). Но не стоит думать, что SQL == NoSQL, так как SQL и NoSQL это разные типы БД, и соответственно подход к работе ними будет разный + количество возможных последствий от инъекций может быть разное. Но перед тем, как мы начнём "ломать" веб-приложения с NoSQL, давайте немного разберём следующее: - Почему NoSQL != SQL, и что вообще такое NoSQL? - А что такое MongoDB и как с ним работать? --- # Почему NoSQL != SQL ## В чём различие В [топике по SQL-инъекциям](https://hackmd.io/@ArroizX/H19vQIU3s) я рассказывал про базы данных: что это, какие есть виды и тд. Также там было рассказано о реляционных базах данных. Если коротко описать реляционные бд, то получится примерно следующее: Это такой тип БД, которая состоит из взаимосвязанных таблиц, каждая таблица хранит определённые данные(одна таблица хранит данные о пользователе, другая о заказах пользователей, третья хранит в себе описание и характеристики товаров на сайте и тд.). Эти таблицы состоят из строк и столбцов. В каждом столбце таблицы хранится определенный тип данных(строка, целое число, булевое значение и тд), в каждой ячейке – значение атрибута(например под столбцом "продано" ячейки будут иметь в качестве значения либо 1 либо 0, то есть булевое значение, где 1 это продано, а 0 - нет ). Каждая строка таблицы представляет собой набор связанных ячеек, относящихся к одному объекту или сущности. ![](https://i.imgur.com/TZx3QvU.png) Для обращения к реляционным БД используется SQL(Structured Query Language). С помощью SQL мы формируем запросы к БД примерно такого вида: `SELECT * FROM products WHERE category = 'Gifts' AND released = 1` Именно на такие SQL-запросы мы и влияли, чтобы приложение не просто делало то, что нужно, а ещё и дополнительно вернуло пароли пользователей, или чтобы обойти аутентификацию и тд. **А что же такое NoSQL?** :::info **NoSQL (Not only SQL)** - это семейство баз данных, которые отказались от использования реалиционной(табличной) модели, и делают упор в сторону schemaless(без схемы, те же реалиционные БД - схемные, так как имеют фиксированную структуру данных, об этом чуть позже). ::: Вообщем, все бд, которые не связаны с SQL, таблицами и которые не имеют фиксированных структур данных - NoSQL! ## Почему используют NoSQL и где их используют? Почему же используют NoSQL? - Гибкая модель данных В SQL прежде чем сохранить данные их надо чётко описать, т.е. если в таблице есть столбцы "name", "gender" и "money" - 3 атрибута, то и данные, которые мы хотим сохранить, должны иметь 3 атрибута, ни больше, ни меньше. В NoSQL можно группировать произвольный набор данных, что может быть полезно для молодых компаний, которые до конца не знают, как их БД должна выглядеть + различные приложения, собирающие данные с разных источников. - Лучше масштабируются В сам дизайн NoSQL заложена поддержка горизонтального масштабирования, благодаря чему не приходится ломать себе голову, как впихнуть лишние данные, как в SQL. - Упор в сторону доступности вместо согласованности Также некоторые NoSQL БД имеют особенность, а именно делают упор в сторону доступности, жертвуя при этом согласлованностью. Что это значит? Вот у нас есть приложение такое, может вы о нём наслышаны, Instagram. Это приложение, которое имеет огромную БД(все посты пользоватей, их личная информация и тд). Доступ к этим данным нужно получать моментально, чтобы пользователь не ждал долгое время - доступность. В то же время неважно если под постом не обновятся лайки, в бд есть запись о текущем количестве лайков, значит согласованность будет потом, но сейчас в приоритете именно доступность данных. Соответственно данная особенность полезна для таких крупных приложений как YouTube, Facebook и др, где есть огромная бд, из которой нужно быстро достать данные. ## Какие виды NoSQL бывают? ![](https://i.imgur.com/lnQXOYE.png) Видов NoSQL БД есть много, но самые ключевые из них следующие: :::warning - **Ключ-значение** (Пример СУБД: **Redis**) В таких БД для доступа к значению используется ключ. Они применяются в качестве хранилищ изображений, специализированных файловых систем, кэшей, информационных платформ для онлайн-игр и т.д. — везде, где главными требованиями являются высокая масштабируемость и минимальная задержка обработки запроса. - **Документо-ориентированные** (Пример СУБД: **MongoDB**) В базах данных этого типа данные записываются в специальный документ в формате JSON или близком к нему. Таким БД свойственны одновременно иерархичность и гибкость. Чаще всего они применяются в системах управления контентом, каталогах, специализированных поисковых системах (например, в электронных архивах). - **Графовые** (Пример СУБД: **Neo4j**) Такие БД сохраняют информацию в виде сложно связанных друг с другом графов. Связанность данных упрощает их хранение, навигацию и поиск. Типичными примерами использования графовых БД являются социальные сети, системы выявления мошенничества. - **Колоночные** (Пример СУБД: **HBase**) В колоночных NoSQL базах данных данные хранятся в ячейках, сгруппированных в колонки, а не в строки данных. Колонки логически группируются в колоночные семейства. Колоночные семейства могут состоять из практически неограниченного количества колонок, которые могут создаваться во время работы программы или во время определения схемы. Чтение и запись происходит с использованием колонок, а не строк. ::: Сегодня мы с вами будем говорить о документо-ориентированных NoSQL БД, а если конкретнее, то об NoSQL инъекциях в MongoDB. С документо-ориентированных проще начинать, да и MongoDB одна из самых популярных СУБД: ![](https://i.imgur.com/biIqyVY.png) --- # MongoDB ## Что это за БД такая? MongoDB, как было сказано ранее, это документо-ориентированная NoSQL СУБД. Появилась она в 2009 году, поэтому её можно считать классической NoSQL-системой. Если кратко её описать то получится следующее: 1) Документо-ориентированная СУБД Все данные в MongoDB хранятся в **документах**. Если мы вспомним реалиционные БД, то у нас там есть **таблицы**, в которых находятся **записи**, в MongoDB же у нас есть **коллекции**, и в них уже хранятся **документы**. ![](https://hackmd.io/_uploads/BJjBbVY82.png) 2) Используются BSON(JSON-подобный синтаксис документов) MongoDB использует подобие JSON, для хранения и передачи данных, что ускоряет её работу. Об этом всём я расскажу чуть позже. 3) JS-подобный синтаксис в shell У данной СУБД, как в том же PostgreSQL, есть своя оболочка, в которой можно управлять данными. Только если в Postgres используются свои определённые команды и синтаксис, которые нужно запоминать, в MongoDB используется JavaScript-подобный синтаксис, который удобно использовать и запоминать, если вы работали с javascript'ом. PostgreSQL ![](https://i.imgur.com/G4QyQWm.png) MongoDB ![](https://i.imgur.com/lsoBYKl.png) ## Что такое JSON? Чуть раньше я упоминал, что в MongoDB используется подобие JSON. Начнём с того, а что такое JSON? :::warning **JSON (JavaScript Object Notation)** - это формат обмена данными, основанный на языке JavaScript. Он является текстовым форматом, который удобен для чтения и записи как человеком, так и компьютером. ::: Формат JSON широко используется в веб-приложениях для передачи данных между сервером и клиентом. JSON представляет собой упрощенную версию языка JavaScript и состоит из пар "ключ-значение", которые разделены запятыми и заключены в фигурные скобки. Ключ представляет собой строку, а значение может быть строкой, числом, логическим значением, массивом, объектом или null. В отличие от XML, JSON более легковесный и проще для чтения и записи, что делает его более популярным для передачи данных в сети. XML ![](https://i.imgur.com/R3dfmzL.jpg) JSON ![](https://i.imgur.com/ZgawvN4.png) Если упростить, то это как XML, вплане того что с помощью него можно хранить и передавать данные, но в более читабельном формате + javascript'у на сайте проще с JSON работать. ## JSON vs BSON А что именно за подобие JSON используется в MongoDB? :::info **BSON (Binary JavaScript Object Notation)** - это компьютерный формат обмена данными. Это бинарная форма представления простых структур данных и ассоциативных массивов (которые называют объектами или документами). Имя «BSON» основано на определении JSON и неофициально значит «Binary JSON» (бинарный JSON). ::: А в чём же различие между JSON и BSON: 1) Документы BSON могут содержать объекты Date или Binary, которые изначально не представлены в чистом JSON. 2) BSON поддерживает различные числовые типы, которые не являются родными для JSON, и многие языки представляют их по-разному. 3) и др. Эта информация скорее просто ради того, чтобы вы знали, что такой BSON и почему JSON != BSON. ## Функции и операторы в MongoDB Как говорилось ранее, вместо SQL-запросов в MongoDB используются JS-подобные команды (хотя это правда отчасти, так как c MongoDB можно работать и с помощью других ЯП, используя специальные библиотеки, но с помощью этих библиотек нельзя выполнять все команды, которые можно делать с помощью JS). Давайте сначала рассмотрим список часто используемых функций, которые можно использовать, чтобы взаимодействовать с БД: ### Функции --- `show dbs;` Команда `show dbs;` используется для отображения списка всех существующих баз данных. Эта команда возвращает список баз данных вместе с их размером. ``` > show dbs; admin 0.000GB local 0.000GB mydb 0.000GB test 0.000GB ``` В этом примере возвращается список четырех баз данных: `admin`, `local`, `mydb`, и `test`. --- `use database_name` Команда `use database_name` в MongoDB используется для переключения на заданную базу данных, в данном случае admin. Если база данных с указанным именем существует, то команда переключит текущую базу данных на эту. Если базы данных с таким именем нет, то она будет создана при первом добавлении в нее данных. Альтернативой также может служить команда `db.getCollectionNames()`, она тоже используется для получения списка имен всех коллекций в текущей базе данных. --- `show collections` При выполнении этой команды MongoDB вернет список имен всех коллекций, находящихся в текущей базе данных, которую вы выбрали с помощью команды use database_name. ``` > show collections orders users ``` --- `db.collection_name.insert()` Команда `db.collection_name.insert()` в MongoDB используется для добавления новых документов в коллекцию. Синтаксис этой команды выглядит следующим образом: ``` db.collection_name.insert( <document or array of documents>, { writeConcern: <document>, ordered: <boolean> } ) ``` - `<document or array of documents>`: Это обязательный параметр, который представляет собой документ или массив документов, которые вы хотите добавить в коллекцию. - `writeConcern`: Опциональный параметр, который определяет уровень подтверждения операции записи. - `ordered`: Опциональный параметр, который указывает, следует ли вставлять документы в порядке, в котором они предоставлены. В качестве примера, следующая команда добавляет новый документ в коллекцию "users": ``` db.users.insert({ name: "John", age: 30, email: "john@example.com" }) ``` Если операция выполнена успешно, MongoDB вернет объект с информацией о количестве добавленных документов, идентификаторе добавленного документа и другой информации. --- `db.collection_name.find().pretty()` Команда `db.collection_name.find().pretty()` используется для отображения документов в коллекции MongoDB в более читабельном формате. Команда `find()` используется для поиска документов в коллекции, результаты выводятся в формате JSON. Однако для больших коллекций и сложных документов это может быть трудно читаемо. Добавление `.pretty()` к команде `find()` форматирует вывод в удобочитаемый вид, добавляя отступы и новые строки для каждого элемента. Это позволяет легче просматривать результаты и делать выводы о структуре и содержании документов. ``` >db.users.find().pretty() { "_id": ObjectId("615e3433e996ab3f246d44de"), "name": "John Doe", "age": 30, "gender": "male" } { "_id": ObjectId("615e3458e996ab3f246d44df"), "name": "Jane Smith", "age": 25, "gender": "female" } { "_id": ObjectId("615e347fe996ab3f246d44e0"), "name": "Bob Johnson", "age": 40, "gender": "male" } ``` --- `db.collection_name.drop()` `db.collection_name.drop()` - это команда для удаления коллекции в MongoDB. В качестве аргумента необходимо указать название удаляемой коллекции. Например, чтобы удалить коллекцию с именем "users", нужно выполнить команду `db.users.drop()`. При этом все документы, хранящиеся в коллекции, также будут удалены. --- `db.collection_name.remove()` Команда `db.collection_name.remove()` используется для удаления одного или нескольких документов из коллекции в MongoDB. Она принимает необязательный параметр-объект, который определяет критерий удаления документов из коллекции. Если этот параметр не указан, будут удалены все документы из коллекции. Например, чтобы удалить все документы из коллекции `users`, можно использовать следующую команду: ``` db.users.remove({}) ``` Эта команда удалит все документы из коллекции `users`. Если нужно удалить документы, удовлетворяющие определенному критерию, например, все документы, где поле `age` меньше 30, можно использовать следующую команду: ``` db.users.remove({ age: { $lt: 30 } }) ``` Эта команда удалит все документы из коллекции `users`, где значение поля `age` меньше 30. --- `db.collection_name.updateOne()` Команда `db.collection_name.updateOne()` используется для обновления одного документа в коллекции MongoDB. Она принимает два обязательных параметра: filter и update, а также необязательные параметры, такие как `upsert`, `arrayFilters` и `collation`. Пример использования: Допустим, есть коллекция `users` с документами вида: ``` { "_id" : ObjectId("615e33b8e7c8a7dc8741c186"), "name" : "John", "age" : 25 } { "_id" : ObjectId("615e33d4e7c8a7dc8741c187"), "name" : "Jane", "age" : 30 } { "_id" : ObjectId("615e33e5e7c8a7dc8741c188"), "name" : "Bob", "age" : 35 } ``` :::info Кстати, для каждого документа, автоматически добавляется поле **_id**. Это поле представляет собой уникальный идентификатор (ObjectId) для каждого документа в коллекции. ::: Для обновления документа с `_id` равным `615e33b8e7c8a7dc8741c186` можно использовать команду: ``` db.users.updateOne({ _id: ObjectId("615e33b8e7c8a7dc8741c186") }, { $set: { age: 30 } }) ``` Эта команда обновит возраст пользователя John на 30. Первый параметр фильтр определяет документ для обновления, а второй параметр обновляет поле возраста с помощью оператора `$set`. :::warning Оператор **$set**: Оператор $set в MongoDB используется в команде обновления для установки значения поля в документе. Он позволяет изменять или добавлять новые поля в существующий документ без изменения остальных полей. ::: Обратите внимание, что если фильтр находит более одного документа, будет обновлен только первый найденный документ. Если нужно обновить все документы, соответствующие фильтру, следует использовать метод `updateMany()` вместо `updateOne()`. --- Остальные команды можно найти [ТУТ](https://www.mongodb.com/docs/manual/reference/method/)! --- ### Операторы запросов Чуть ранее я продемонстрировал оператор `$set`, но есть множество и других операторов, которые мы будем использовать для NoSQL-инъекций! #### Сравнение `$eq` - Соответствует значениям, равным указанному значению. `db.users.find({age:{$eq:32}}).pretty()` Оператор $eq используется для сравнения значений поля с конкретным значением. В данном случае, команда ищет все документы, где значение поля age равно 32. --- `$gt` - Соответствует значениям, которые больше указанного значения. --- `$gte` - Соответствует значениям, которые больше или равны указанному значению. --- `$in` - Соответствует любому из значений, указанных в массиве. --- `$lt `- Соответствует значениям, которые меньше указанного значения. --- `$lte` - Соответствует значениям, которые меньше или равны указанному значению. --- `$ne` - Соответствует всем значениям, которые не равны указанному значению. --- `$nin` - Не соответствует ни одному из значений, указанных в массиве. `db.users.find({ login: { $nin: [ "arroiz", "admin", ...] } })` #### Логические `$and` - Позволяет объединить несколько выражений в запросе и выполнить их все одновременно. `db.collection_name.find({ $and: [ { field1: value1 }, { field2: value2 } ] })` В данном примере мы ищем все документы в коллекции collection_name, у которых значение поля field1 равно value1, и значение поля field2 равно value2. Оба условия должны выполняться одновременно, чтобы документ был выбран. --- `$not` - Инвертирует эффект выражения запроса и возвращает документы, которые не соответствуют выражению запроса. `db.collection_name.find({ field_name: { $not: { $gt: 5 } } })` Эта команда ищет все документы в коллекции collection_name, где значение поля field_name НЕ больше 5. Здесь оператор $not содержит внутри себя оператор $gt, который означает "больше". Таким образом, данная команда ищет все документы, где значение поля field_name не больше 5. --- `$nor` - Объединяет предложения запроса с логическим NOR возвратом всех документов, которые не соответствуют обоим предложениям. --- `$or` - Объединяет предложения запроса с логическим OR возвратом всех документов, которые соответствуют условиям любого предложения. --- `$exists` - это оператор сравнения в MongoDB, который используется для проверки наличия поля в документе. Оператор `$exists` принимает булево значение (true или false) в качестве значения оператора. Он проверяет, существует ли поле в документе и соответствует ли указанному значению оператора. Результатом операции будет true, если поле существует и соответствует условию, или false в противном случае. ``` // Поиск документов, у которых поле "age" существует db.collection.find({ age: { $exists: true } }) // Поиск документов, у которых поле "address" не существует db.collection.find({ address: { $exists: false } }) ``` #### Поиск `$regex` - Выбирает документы, значения которых соответствуют указанному регулярному выражению. ``` // Поиск документов, в которых поле "name" начинается с "J" db.collection.find({ name: { $regex: "^J" } }) // Поиск документов, в которых поле "email" содержит строку "example.com" db.collection.find({ email: { $regex: "example.com" } }) ``` --- `$text` - Выполняет текстовый поиск. Оператор $text принимает в качестве значения поисковую фразу или фразу с ключевыми словами и выполняет поиск документов, содержащих эту фразу в текстовых полях, проиндексированных для полнотекстового поиска. Пример использования оператора $text: ``` // Создание текстового индекса для поля "description" db.collection.createIndex({ description: "text" }) // Поиск документов, содержащих слово "example" в поле "description" db.collection.find({ $text: { $search: "example" } }) ``` --- `$where` - Соответствует документам, удовлетворяющим выражению JavaScript. Оператор `$where` в MongoDB позволяет выполнить поиск по документам в коллекции, используя JavaScript-выражение в качестве условия. Пример команды MongoDB с оператором `$where`: ``` db.collection_name.find( { $where: "this.x + this.y === 10" } ) ``` В данном примере мы ищем документы в коллекции `collection_name`, у которых сумма значений полей `x` и `y` равна 10. Вместо строки выражения в качестве значения оператора `$where` можно использовать функцию, которая будет выполнена для каждого документа в коллекции. Например: ``` db.collection_name.find( { $where: function() { return this.x + this.y === 10; } } ) ``` В этом примере мы используем анонимную функцию в качестве значения оператора `$where`. Эта функция возвращает `true` для документов, у которых сумма значений полей `x` и `y` равна 10, и `false` для остальных документов. --- Теперь, когда мы ознакомились с основами по MongoDB, можно наконец переходить к NoSQL-инъекциям в MongoDB! --- # Какие инструменты можно использовать? Начнём с того, что определимся с тем, какие инструменты можно использовать: 1) Burp Suite + плагины ![](https://hackmd.io/_uploads/r1k8_m_N2.png) 2) Owasp ZAP ![](https://hackmd.io/_uploads/rkVddQ_Vh.png) 3) [NoSQLMap](https://github.com/codingo/NoSQLMap) ![](https://hackmd.io/_uploads/rknwo7OVh.jpg) 4) [NoSQL Injector](https://github.com/Charlie-belmer/nosqli) ![](https://hackmd.io/_uploads/HJVbf4KEn.png) --- # Использование NoSQL Injection? ## Как определить NoSQL Если вспомнить SQL-инъекции, то там основным методов определения наличия уязвимости была вставка кавычки во все поля ввода пользователя, и если это не слепая SQL-инъекция, то может появиться ошибка, благодаря которой мы можем понять, что именно этот ввод надо "ковырять" дальше: ![](https://hackmd.io/_uploads/BysegLv8h.png) В MongoDB NoSQL есть примерно такой же способ определения наличия уязвимости NoSQL-injection, только список специальных символов побольше: `' " \ / ; $ { } [] .` Если приложение вернуло ошибку после этих символов, то есть шанс, что вы нашли то самое место, где есть NoSQL injection ![](https://hackmd.io/_uploads/BkZZjJFN3.png) Также можно вместо различных данных подставить ключевые слова NoSQL: $ne, $eq, $where и др. И ещё можно отправить дополнительные объекты вместе с действительным JSON: `{"user": "nullsweep"}` -> `{"user":["nullsweep","foo"]}` Это тоже может вызвать ошибку. Как и попытка отпраки несуществующих операторов: $nensdkfn и др извращения=) ## Как можно использовать NoSQL Injection Теперь можно наконец перейти к тому, как экплуатировать NoSQL-инъекции=) И начнём с обхода аутентификации: ### Обход аутентификации :::info **Лабораторная**: https://www.root-me.org/ru/Zadachi-i-problemy/Veb-server/NoSQL-injection-Authentication ::: У нас есть сайт с механизмом аутентификации: ![](https://hackmd.io/_uploads/Bk2EbPP8h.png) При вводе несуществующих учётных данных выдаётся такая страничка: ![](https://hackmd.io/_uploads/SkVPFPwI3.png) Также можно обратить внимание, что параметры, содержащие учётные данные находятся в строке запроса: ![](https://hackmd.io/_uploads/rJq2KvD83.png) Скорее всего эти параметры и являются точками входа, в которых есть NoSQL инъекции. Но как их использовать? Обычно если данные передаются в JSON, то мы можем сделать обход аутентификации, отправив примерно такой JSON в запросе: ``` { "login":{"$ne": null}, "pass":{"$ne": null} } ``` **Что это всё делает:** Здесь мы вместо значений параметров `login` и `pass` передаём словари с оператором сравнения `$ne`(not equal). И поэтому данный запрос в MongoDB выглядит теперь не как `find({"login":"someValue","pass":"someValue"})`, который ищет аккаунт в БД, и если он есть, то происходит успешная аутентификация пользователя(всё как в SQL), а как `find({"login":{"$ne": null},"pass":{"$ne": null}})`, который просто ищет все документы, значение параметров login и pass которых не равен null(то есть не пустое значение). И таким образом БД точно вернёт какие-то записи -> мы будем залогинены под тем пользователем, чья запись первая "выскочиала" для БД. :::info **Немного картиночек, чтобы лучше понять вышесказанное:** Вот у нас есть коллекция users. В ней есть документы, которые имеют в себе поля, связанные с логинами и паролями пользователей приложения: ![](https://hackmd.io/_uploads/ryxLVetI2.png) Предположим, что, когда пользователь хочет аутентифицироваться, он отправляет HTTP запрос со своим логином и паролем(пусть логин - root, пароль - p@ssw0rd). Тогда запрос к БД будет `find({"login":"root","pass":"p@ssw0rd"})`. И если такой пользователь есть в БД, то вернётся 1 документ: ![](https://hackmd.io/_uploads/SynMBxYU3.png) А теперь вместо такого обычного запроса будет отправлен `find({"login":{"$ne":"x"},"password":{"$ne":"x"}})`. Он уже вернёт все документы, чьи поля login и password != x, то есть все из БД. И первый доркумент будет с логином root, и скорее всего пользователь будет залогинен под ним: ![](https://hackmd.io/_uploads/BkOhSet82.png) Таким образом, злоумышленник может залогинется либо под любым пользователем, либо под каким-то определённым, если знает его логин, а вместо пароля подставит конструкцию с оператором `$ne`. ::: Но это если бы у нас был JSON, в данном случае у нас обычные HTTP параметры, поэтому может показаться, что мы не можем создать подобный "массив" -> нельзя эксплуатировать NoSQL :( К счастью для нас, **PHP и многие другие ЯП позволяют передавать массив**, используя следующую конструкцию в теле POST-запроса: `login[$ne]=x&pass[$ne]=x` Это и есть аналог той конструкции в JSON, благодаря которой мы можем попросить бд вернуть документ, содержащий login и pass, которые не равны х, то есть 100% какой-нибудь документ, соответственно мы будем залогинены под аккаунтом того документа, который первый попался БД на глаза: ![](https://hackmd.io/_uploads/Hk63I2uU2.png) Если например мы не хотим заходить под админом, то можем спокойно x в login заменить на admin, думаю суть понятна тут:) ![](https://hackmd.io/_uploads/SkaGDndLn.png) Помимо оператора `$ne` можно использовать и другие операторы: `$gt`, `$lt`, `$gte` и др. И в конце хочу быстро упомянуть ещё один полезный оператор `$nin`. С помощью него мы можем фильтровать документы по определённому полю, причём примерно так: покажи мне все документы, у которых значение поля login не равняется ни одному значению из списка. ![](https://hackmd.io/_uploads/BkydKgY8n.png) И таким образом, можно решить таск с рутми, так как мы залогинемся ни как admin, и ни как test. Только можно увидеть, что конструкция требует массив в массиве, то есть пэйлоад слегка изменится: ![](https://hackmd.io/_uploads/B1L1oxKLh.png) ### Извлечение данных :::info **Лабораторная**: https://www.root-me.org/ru/Zadachi-i-problemy/Veb-server/NoSQL-injection-Authentication ::: А что если мы захотим не просто войти под кем-либо, а вытащить пароли пользователей? Мы так тоже можем сделать:) Здесь нам будет полезен оператор `$regex`, с помощью которого мы сможем вытаскивать из БД пароли пользователей, используя регулярные выражения! #### Извлечение длины данных Для начала надо понять какой длины пароль, чтобы потом посимвольно сбрутить его. Нам в этом поможет выражение вида `parameter[$regex]=^.{число точек} или parameter[$regex]=^..............$`. :::warning **Что это всё значит?** **Регулярные выражения (Regular expression, regex)**— формальный язык, используемый в компьютерных программах, работающих с текстом, для поиска и осуществления манипуляций с подстроками в тексте, основанный на использовании метасимволов. Для поиска используется строка-образец, состоящая из символов и метасимволов и задающая правило поиска. Давайте разберём сначало второе выражение - `parameter[$regex]=^..............$`: 1) Используем оператор `$regex`, чтобы делать поиск докуменов по определённым значениям какого-то поля, используя регулярные выражения. 2) Знак `^` означает начало строки, всё, что идёт после него связано со строкой, а не с правилами регулярного выражения. 3) Точки означают символы. То есть, у нас здесь 14 точек, а значит мы ищем строку, в которой 14 символов, или в которой 14 или более символов, выбор поиска объяснён в 3 пункте. 4) Символ `$` означает окончание строки. В нашем случае он есть, так что поиск будет вести по 14 символам. Если бы его не было, то поиск бы был по 14 или более символов. Если же мы возьмём первое регулярное выражение, а именно `parameter[$regex]=^.{число точек}`, то здесь всё тоже самое, только есть одна разница: Здесь указывается точка, и потом в фигурных скобках их количество. Это проще использовать, чем второе регулярное выражение, потому что символов в пароле может быть очень много. ::: И всё, что надо вначале, это подгадать кол-во точек, и если не будет ошибки, то значит можно переходить к бруту. ![](https://hackmd.io/_uploads/rypQP-FU2.png) ![](https://hackmd.io/_uploads/ByjKP-KU2.png) #### Извлечение значения данных После определения кол-ва символов можно начинать делать самое скучное, но важное действие - брут посимвольно пароля. Для этого мы делаем следующее: 1) Перед точкой ставим любой символ, а количество точек уменьшаем, чтобы не было ошибки(если у вас нет знака окончания строки, то всё равно рекомендую уменьшать потихоньку). ![](https://hackmd.io/_uploads/SJBK9WtI2.png) 2) Включаем intercept в Бурпе и обновляем страничку с изменённым параметром. После перехватываем запрос с параметром и отправляем в Intruder: ![](https://hackmd.io/_uploads/HJsSsbKIh.png) ![](https://hackmd.io/_uploads/ByI8ibt8h.png) ![](https://hackmd.io/_uploads/S1gOjWFIn.png) 3) В Intruder выделяем подставленный символ. Дальше во вкладке Payloads выбираем тип brute forcer, в качестве знаков подставновки выбираем англ. буквы в нижнем и в верхем регистре, цифры и пару спецсиволов, и выставляем, что брутаем только 1 символ, а не 4: ![](https://hackmd.io/_uploads/BkhW3WFIn.png) ![](https://hackmd.io/_uploads/Byr7n-F8n.png) 4) Запускаем перебор и ищем отличающийся по длине ответ: ![](https://hackmd.io/_uploads/rkau3btI2.png) ![](https://hackmd.io/_uploads/Hkt9hZFLn.png) 5) Отлично, мы нашли первый символ - n. Теперь ставим новый символ, выделяем его, уменьшаем количество точек и брутаем опять:) ![](https://hackmd.io/_uploads/BJflpbK8h.png) ![](https://hackmd.io/_uploads/HJQ-TbY8n.png) И дальше по сути повторяем эти действия, пока не набрутим пароль! Если что, можно таким способом доставать не только пароли, но и другие данные из бд. ### Выполнение произвольных функий (PHP) Используя оператор $func библиотеки MongoLite(который используется по умолчанию) в php, можно выполнять произвольные функции: ![](https://hackmd.io/_uploads/B1p_CbFIn.png) `"user":{"$func": "var_dump"}` ### Извлечение данных из других коллекций Можно использовать оператор `$lookup` для получения информации из другой коллекции. Например, ниже мы читаем из другой коллекции (users) и получаем результаты всех записей с паролем, соответствующим подстановочному знаку, то есть всё: ``` [ { "$lookup":{ "from": "users", "as":"resultado","pipeline": [ { "$match":{ "password":{ "$regex":"^.*" } } } ] } } ] ``` ### Инъекции в регулярных выражениях :::info **Лабораторная**: Собранная со статьи, ссылка была во введении! ::: :::warning **Инъекции в регулярных выражениях (regular expression injections)** - это уязвимость, которая возникает, когда неправильно обрабатываются пользовательские данные, вставляемые в регулярное выражение. Это может привести к выполнению нежелательного кода или изменению поведения регулярного выражения. ::: Чтобы попрактиковаться зайдём в раздел "Инъекции в регулярных выражениях": ![](https://hackmd.io/_uploads/Hk7qSGYU3.png) Тут у нас страничка с аутентификацией, давайте посмотрим на код, и попытаемся понять, как аутентификация работает: ![](https://hackmd.io/_uploads/r1XMIzYIn.png) Как мы видим, у нас тут обычная функция `find({login: login, password: password})`, и вроде ничего такого, только вот поиск по полю пароля у нас через регулярное выражение(тот самый `$regex`). Поэтому мы можем обойти тут аутентификацию несколькими способами(но во всех них надо знать логин, который существует в БД): 1) Так как нет символа окончания строки, можно просто не писать пароль, таким образом будут просто выбраны все документы с любым паролем и нужным логином ![](https://hackmd.io/_uploads/Bk-eOzKUh.png) ![](https://hackmd.io/_uploads/HyheufYI2.png) 2) Использовать регулярное выражение `[\s\S]*` (любой символ (включая пробелы и символы новой строки) повторяющийся ноль или более раз): ![](https://hackmd.io/_uploads/ryQcOftL3.png) ![](https://hackmd.io/_uploads/r1d9dfKL3.png) 3) Просто написать часть пароля: ![](https://hackmd.io/_uploads/BJ4n_ztL3.png) ![](https://hackmd.io/_uploads/SJihuGYI3.png) ### JSON-инъекции ![](https://hackmd.io/_uploads/S1egYGFLn.png) Здравствуй снова, страничка аутентификации:) Давайте посмотрим в код: ![](https://hackmd.io/_uploads/ByZN9fKLh.png) Код преобразует текстовое представление объекта JavaScript (запроса к MongoDB) в объект. После передачи этого объекта в БД происходит аутентификация пользователя, через функцию find(). В этом куске кода есть одно очень слабое место — входные данные никак не фильтруются и не санитизируются, поэтому можно сформировать практически любой запрос к базе! Например, если ввести `root'})//` в логине, а пароль не указывать то можно войти в систему! ![](https://hackmd.io/_uploads/BJxWjzFUh.png) ![](https://hackmd.io/_uploads/H1DbiMFUn.png) Как так получилось? Все очень просто, это похоже на SQL-инъекции: У нас есть запрос к БД вида `db.users.find({login:'root',password:'somePassword'})`. Наши данные находятся в кавычках. А раз данные не фильтрутся, то можно закрыть кавычку и выйти в запрос, потом закрыть функцию с помощью `})`, а дальше просто закомментировать оставшуются часть запроса с помощью `//`, и наш запрос из: `db.users.find({login:'root',password:'somePassword'})` Превратится в: `db.users.find({login:'root'})//',password:'somePassword'})` `db.users.find({login:'root'})` Это эквивалентно SQL-инъекции: ``` SELECT * FROM users WHERE username='root'--' AND password='somePassword' SELECT * FROM users WHERE username='root' ``` На самом деле этот скрипт даже еще опаснее, так как с его помощью можно выполнить любой код JavaScript на веб-сервере. Например, имя пользователя "' + process.execPath})//" сформирует запрос вида `db.users.find({ login: 'C:\\node.exe' })` ### Внедрение JavaScript кода ![](https://hackmd.io/_uploads/ByaQJQF8n.png) Большинство современных реляционных СУБД позволяют создавать хранимые процедуры. Давайте рассмотрим Microsoft SQL Server, который расширяет возможности ANSI SQL и позволяет соблюдать практически любые требования бизнес-приложений. Если не хватает возможностей T-SQL (это диалект SQL, реализованный в SQL Server), то можно написать хранимую процедуру на C# или любом другом .NET-совместимом языке. MongoDB обладает не менее богатыми возможностями, в число которых входит серверный JavaScript. Фактически можно выполнить почти любой код на сервере баз данных(то есть не просто писать запросы к бд на JS, а именно создавать своего рода макросы). С одной стороны, это позволяет писать очень сложные приложения для обработки данных, с другой — делает приложение более уязвимым. **Где можно использовать JavaScript в MongoDB?** 1) **Запросы с оператором $where**. Например, запрос `db.orders.find({ $where: "this.amount > 3" })` - вернет список заказов, количество пунктов в которых больше трех. 2) **Команда db.eval**. К примеру, `db.eval("function (x) { return x * x; }", 2)` - вернет четыре. 3) **Функции для сохранения в базе данных**. MongoDB позволяет сохранять функции, написанные на языке JavaScript, в базе данных. Для этого используется специальная системная коллекция system.js. Чтобы создать новую хранимую функцию foo(x), нужно выполнить следующее: `db.system.js.save( { _id: "foo", value: function (x) { return x * x; }})` Теперь можно вызвать ее вот так: db.eval("foo(2)"). 4) **Map/Reduce**. Map/Reduce — это программный фреймворк, разработанный компанией Google для параллельных вычислений над большими объемами данных. Он содержит две операции: map, используемую для предварительной обработки данных, и reduce, осуществляющую поиск результата. MongoDB позволяет запускать операции map/reduce на сервере баз данных. Операции по распараллеливанию процессов и агрегации СУБД берет на себя, от разработчика требуется лишь указать исходные данные и функции, реализующие команды map и reduce. Теперь закончив с объяснениями, давайте перейдём к лабе `$where`, и посмотрим на её код: ![](https://hackmd.io/_uploads/HJ02WmtU2.png) Сначала генерируется скрипт, который проверяет имя и пароль пользователя. К сожалению, данные в переменных password и login никак не проверяются, что позволяет выполнить любой скрипт на сервере. Теперь давай попробуем войти как root. Введём имя пользователя «root' //» и попробуй войти. И залогинемся! Это возможно благодаря тому, что на сервере был сформирован следующий запрос к MongoDB: { '$where': 'this.login === \'root\' //\' && this.password === \'\'' } "//" — это комментарий в JavaScript, поэтому результирующий запрос примет вид «this.login === 'root'». :::info P.S. К сожалению не залогинемся так как лаба поломана, и она выкидывает 500 ошибку... ![](https://hackmd.io/_uploads/S1_B4mFUh.png) Но это точно бы сработало:) **UPD** Лабу можно решить, но очень странным пэйлоадом: `'; while(true){return 1}'` ![](https://hackmd.io/_uploads/ByAJOmFUn.png) ![](https://hackmd.io/_uploads/BkBxOXFIn.png) Честно говоря, я без понятия как так работает, потому что наткнулся случайно=) ::: К счастью, в момент выполнения запроса база данных находится в режиме «Только чтение», поэтому злоумышленник не сможет написать скрипт, модифицирующий данные. Но это еще не говорит о том, что подобная атака невозможна. Для подтверждения этого есть раздел «Инъекции JavaScript — db.eval(...)». ![](https://hackmd.io/_uploads/SJNqV7YL3.png) На этот раз аутентификация происходит посредством вызова функции eval на сервере базы данных: ![](https://hackmd.io/_uploads/H1EiN7FUh.png) И тут уже есть возможность выполнить любой код на сервере БД. Можно создать нового пользователя pen_test с паролем pen_test. Для этого нужно ввести следующий логин: `'}), db.users.insert({login: 'pen_test', password: 'pen_test'}), 1 } //` 1) Происходи вход в систему. 2) В БД появился новый пользователь pen_test. И опять таки лаба сломана, так как функцию eval() выпилили в одной из новых версий, но если вы встретите старую mongodb, то можете проверить:) ### DoS Вернёмся к лабе с $where! Можно закрыть кавычку и сделать бесконечный цикл while, обработать который приложение не сможет, и если приложение плохо сделано, оно может лечь: `'; while(true){}'` Тут видно по вкладке, что приложение думает: ![](https://hackmd.io/_uploads/H1oBjmtI3.png) А вот и ошибка: ![](https://hackmd.io/_uploads/ryB_imKIn.png) ### REST API :::info REST API в лабе не работает, так как её вырезали из MongoDB, из-за того, что это не нужно и не безопасно! ::: В MongoDB входил простой REST-интерфейс который позволял получить доступ к данным в режиме «Только чтение». Кроме того, существовал проект под названием Sleepy Mongoose, который реализует полную поддержку REST. На странице «Манипуляции с REST-интерфейсом» лабы происходит аутентификация пользователя при помощи REST-интерфейса MongoDB: ![](https://hackmd.io/_uploads/HyYuA7YLn.png) Скрипт формирует REST-запрос, игнорируя при этом все данные после символа "#". Когда REST-запрос готов, скрипт формирует HTTP-запрос к серверу и ожидает результат в формате JSON. К примеру, запрос информации о пользователе root в базе данных secure_nosql выглядит следующим образом: `http://127.0.0.1:28017/secure_nosql/users/?filter_login=root&filter_password=p@ssw0rd`. Всё бы хорошо, но в коде есть ошибка, проявляющая себя при обработке символа "#". Если войти с именем «root#», то можно войти в систему. Проблема, обусловленная формированием следующего URL: `http://localhost:28017/secure_nosql/users/?filter_login=root#&filter_password=`. состоит в том, что параметр filter_password был проигнорирован и аутентификация проходила посредством запроса `http://localhost:28017/secure_nosql/users/?filter_login=root`. Стоит отметить, что большинство REST-интерфейсов также уязвимы к подделке межсайтовых запросов (CSRF): `<img src="http://localhost:28017/secure_nosql/users/" />` ### Blind NoSQL-Injection :::info **Лабораторная**: https://www.root-me.org/ru/Zadachi-i-problemy/Veb-server/NoSQL-injection-Blind?lang=ru ::: Также как и с большинством уязвимостей, в NoSQL-injection может быть такая ситуация, когда веб приложение уязвимо, но не подаёт каких то заметных признаков/инфу, из-за чего приходится всё делать вслепую, ориентируясь на такие вещи как: время ответа, длина возвращаемого контента и др. В этой лабораторной нужно извлечь флаг таска nosqlblind, манипулируя значением параметра flag. По сути, это всё то же использование оператора `$regex`, который был рассмотрен ранее, так что думаю, вы сможете достать флаг и из без меня:) --- # Домашка(по желанию) На этом у меня всё! Надеюсь, что вам было интересно и понятно, а для закрепления, советую сделать следующее: 1) Потыкать лабу, ссылка на которую есть в введении и прислать скрины с успешной эксплуатации уязвимости. 2) Выполнить следующие лабы rootme и прислать скрины: [ВОТ](https://www.root-me.org/ru/Zadachi-i-problemy/Veb-server/NoSQL-injection-Blind) и [ВОТ](https://www.root-me.org/ru/Zadachi-i-problemy/Veb-server/NoSQL-injection-Authentication) Если есть вопросы - пишите в тг @ArroizX. ![](https://i.imgur.com/oIUPQIW.jpg)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully